1
2
3
4
5
6
7
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
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"""
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
99
100
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
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
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
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
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
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
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
253 @property
255 @property
256 - def molwt(self): return self.Mol.molecularWeight()
258 return self.Mol.name()
259 - def _settitle(self, val): self.Mol.setName(val)
260 title = property(_gettitle, _settitle)
261 @property
263 if not self.Mol.hasZCoord():
264 return (0, self.write("can"))
265 else:
266 return (1, self.write("mol"))
267
269 """Add hydrogens."""
270 self.Mol.unfoldHydrogens()
271
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
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
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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
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
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
471 @property
473 @property
475 return tuple(self.Atom.xyz())
476 @property
478
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
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 """
506 """Initialise with a SMARTS pattern."""
507 try:
508 self.smarts = indigo.loadSmarts(smartspattern)
509 except IndigoException:
510 raise IOError, "Invalid SMARTS pattern."
511
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
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 """
558 if not self._mol.hasProperty(key):
559 raise KeyError, "'%s'" % key
561 return [prop.name() for prop in self._mol.iterateProperties()]
563 return [prop.rawData() for prop in self._mol.iterateProperties()]
565 return [(prop.name(), prop.rawData())
566 for prop in self._mol.iterateProperties()]
568 return iter(self.keys())
570 return iter(self.items())
572 return len(self.keys())
574 return self._mol.hasProperty(key)
576 self._testforkey(key)
577 self._mol.removeProperty(key)
579 for key in self:
580 del self[key]
583 - def update(self, dictionary):
584 for k, v in dictionary.iteritems():
585 self[k] = v
587 self._testforkey(key)
588 return self._mol.getProperty(key)
590 self._mol.setProperty(key, str(value))
593
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 """
610 self.fp = fingerprint
612 return indigo.similarity(self.fp, other.fp, "tanimoto")
614 stringrep = self.fp.toString()
615 return [int(stringrep[i:i+1]) for i in range(0, len(stringrep), 1)]
616 @property
618 return _findbits(self._buffer_to_int(), 8)
620 return str(self._buffer_to_int())
621
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
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
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__":
672 import doctest
673 doctest.testmod()
674