Add drawMols3D() and improve documentation for IPythonConsole (#8083)

* Fixes #8082
more docs

* changes in response to review
This commit is contained in:
Greg Landrum
2024-12-14 18:21:55 +01:00
committed by GitHub
parent 736e309f10
commit b7de0585ca

View File

@@ -34,21 +34,38 @@ from PIL import Image
from PIL.PngImagePlugin import PngInfo
molSize = (450, 150)
highlightSubstructs = True
kekulizeStructures = True
highlightByReactant = False
ipython_useSVG = False
ipython_showProperties = True
ipython_maxProperties = 10
ipython_3d = False
molSize_3d = (400, 400)
drawing_type_3d = 'stick' # default drawing type for 3d structures
bgcolor_3d = '0xeeeeee'
drawOptions = rdMolDraw2D.MolDrawOptions()
highlightSubstructs = True # highlight substructure matches when drawing 2D structures
kekulizeStructures = True # try to kekulize structures before drawing
highlightByReactant = False # highlight reactions by reactant
ipython_useSVG = False # use the SVG renderer for 2D depictions (otherwise PNG is used)
ipython_showProperties = True # display molecule properties when rendering them in 2D
ipython_maxProperties = 10 # maximum number of properties to display
ipython_3d = False # try to use 3D rendering for molecules with a 3D conformer
molSize_3d = (400, 400) # default size of 3D structures
drawing_type_3d = 'stick' # default drawing type for 3d structures
bgcolor_3d = '0xeeeeee' # default background color for 3d structures
drawOptions = rdMolDraw2D.MolDrawOptions() # drawing options for 2D structures
InteractiveRenderer._defaultDrawOptions = drawOptions
def addMolToView(mol, view, confId=-1, drawAs=None):
''' adds a single molecule to a py3Dmol view
Parameters
----------
m : RDKit molecule
the molecule to draw
view : py3Dmol.view
the view to draw into
confId : int
the conformer ID to draw. If not provided, the default conformer (-1) will be used
drawAs : str
the drawing type to use. If not provided, 'stick' will be used for small molecules,
'cartoon' for large (more than 999 atoms) molecules
Returns
-------
None'''
if mol.GetNumAtoms() >= 999 or drawAs == 'cartoon':
# py3DMol is happier with TER and MASTER records present
pdb = Chem.MolToPDBBlock(mol, flavor=0x20 | 0x10)
@@ -64,6 +81,26 @@ def addMolToView(mol, view, confId=-1, drawAs=None):
def drawMol3D(m, view=None, confId=-1, drawAs=None, bgColor=None, size=None):
''' draws a single molecule in 3D using py3Dmol
Parameters
----------
m : RDKit molecule
the molecule to draw
view : py3Dmol.view
the view to draw into. If not provided, one will be created
confId : int
the conformer ID to draw. If not provided, the default conformer (-1) will be used
drawAs : str
the drawing type to use. If not provided, 'stick' will be used
bgColor : str
the background color to use for the drawing. If not provided, the default background color will be used
size : tuple of int
the size of the drawing. If not provided, molSize_3d will be used
Returns
-------
the py3Dmol.view object containing the drawing'''
if bgColor is None:
bgColor = bgcolor_3d
if size is None:
@@ -82,6 +119,55 @@ def drawMol3D(m, view=None, confId=-1, drawAs=None, bgColor=None, size=None):
view.zoomTo()
return view.show()
def drawMols3D(mols, view=None, confIds=None, drawAs=None, bgColor=None, size=None, removeHs=False,
colors=('cyanCarbon','redCarbon','blueCarbon','magentaCarbon','yellowCarbon','cornflowerblueCarbon')):
''' draws a list/tuple of molecules in 3D using py3Dmol
Parameters
----------
mols : sequence (list, tuple, etc.) of RDKit molecules
the molecules to draw
view : py3Dmol.view
the view to draw into. If not provided, one will be created
confIds : sequence (list, tuple, etc.) of int
the conformer ID to draw for each molecule. If not provided, the default conformer (-1) will be used
drawAs : str or sequence of str
the drawing type to use for each molecule. If not provided, 'stick' will be used
bgColor : str
the background color to use for the drawing. If not provided, the default background color will be used
size : tuple of int
the size of the drawing. If not provided, molSize_3d will be used
removeHs : bool
whether or not to remove Hs from the molecules before drawing, the default is False
colors : sequence of str
the colors to use for drawing the molecules.
Returns
-------
the py3Dmol.view object containing the drawing'''
if bgColor is None:
bgColor = bgcolor_3d
if size is None:
size = molSize_3d
if view is None:
view = py3Dmol.view(width=size[0], height=size[1])
if drawAs is None:
drawAs = ['stick']*len(mols)
if confIds is None:
confIds = [-1] * len(mols)
for m,confId in zip(mols, confIds):
if removeHs:
m = Chem.RemoveHs(m)
addMolToView(m, view, confId)
for i in range(len(mols)):
view.setStyle({'model': i,}, {drawAs[i]: {'colorscheme': colors[i % len(colors)]}})
view.setBackgroundColor(bgColor)
view.zoomTo()
return view.show()
def _toJSON(mol):
"""For IPython notebook, renders 3D webGL objects."""
@@ -186,8 +272,8 @@ def _toReactionSVG(rxn):
def _toMolBundlePNG(bundle):
if _MolsToGridImageSaved is not None:
fn = _MolsToGridImageSaved
if Draw._MolsToGridImageSaved is not None:
fn = Draw._MolsToGridImageSaved
else:
fn = Draw.MolsToGridImage
return fn(bundle, subImgSize=molSize, drawOptions=drawOptions, useSVG=False, returnPNG=True)
@@ -196,8 +282,8 @@ def _toMolBundlePNG(bundle):
def _toMolBundleSVG(bundle):
if not ipython_useSVG:
return None
if _MolsToGridImageSaved is not None:
fn = _MolsToGridImageSaved
if Draw._MolsToGridImageSaved is not None:
fn = Draw._MolsToGridImageSaved
else:
fn = Draw.MolsToGridImage
return fn(bundle, subImgSize=molSize, drawOptions=drawOptions, useSVG=True)
@@ -239,21 +325,18 @@ def display_pil_image(img):
return bio.getvalue()
_MolsToGridImageSaved = None
from IPython import display
def ShowMols(mols, maxMols=50, **kwargs):
global _MolsToGridImageSaved
if 'useSVG' not in kwargs:
kwargs['useSVG'] = ipython_useSVG
if 'returnPNG' not in kwargs:
kwargs['returnPNG'] = True
if InteractiveRenderer.isEnabled():
fn = InteractiveRenderer.MolsToHTMLTable
elif _MolsToGridImageSaved is not None:
fn = _MolsToGridImageSaved
elif Draw._MolsToGridImageSaved is not None:
fn = Draw._MolsToGridImageSaved
else:
fn = Draw.MolsToGridImage
@@ -277,10 +360,6 @@ def ShowMols(mols, maxMols=50, **kwargs):
return display.Image(data=res, format='png')
return res
ShowMols.__doc__ = Draw.MolsToGridImage.__doc__
def _DrawBit(fn, *args, **kwargs):
if 'useSVG' not in kwargs:
kwargs['useSVG'] = ipython_useSVG
@@ -302,66 +381,35 @@ def _DrawBits(fn, *args, **kwargs):
sio = BytesIO(res)
return Image.open(sio)
_DrawMorganBitSaved = None
def DrawMorganBit(mol, bitId, bitInfo, drawOptions=drawOptions, **kwargs):
global _DrawMorganBitSaved
if _DrawMorganBitSaved is not None:
fn = _DrawMorganBitSaved
if Draw._DrawMorganBitSaved is not None:
fn = Draw._DrawMorganBitSaved
else:
fn = Draw.DrawMorganBit
return _DrawBit(fn, mol, bitId, bitInfo, drawOptions=drawOptions, **kwargs)
DrawMorganBit.__doc__ = Draw.DrawMorganBit.__doc__
_DrawMorganBitsSaved = None
def DrawMorganBits(*args, drawOptions=drawOptions, **kwargs):
global _DrawMorganBitsSaved
if _DrawMorganBitsSaved is not None:
fn = _DrawMorganBitsSaved
if Draw._DrawMorganBitsSaved is not None:
fn = Draw._DrawMorganBitsSaved
else:
fn = Draw.DrawMorganBits
return _DrawBit(fn, *args, drawOptions=drawOptions, **kwargs)
DrawMorganBits.__doc__ = Draw.DrawMorganBits.__doc__
_DrawRDKitBitSaved = None
def DrawRDKitBit(mol, bitId, bitInfo, drawOptions=drawOptions, **kwargs):
global _DrawRDKitBitSaved
if _DrawRDKitBitSaved is not None:
fn = _DrawRDKitBitSaved
if Draw._DrawRDKitBitSaved is not None:
fn = Draw._DrawRDKitBitSaved
else:
fn = Draw.DrawRDKitBit
return _DrawBit(fn, mol, bitId, bitInfo, drawOptions=drawOptions, **kwargs)
DrawRDKitBit.__doc__ = Draw.DrawRDKitBit.__doc__
_DrawRDKitBitsSaved = None
def DrawRDKitBits(*args, drawOptions=drawOptions, **kwargs):
global _DrawRDKitBitsSaved
if _DrawRDKitBitsSaved is not None:
fn = _DrawRDKitBitsSaved
if Draw._DrawRDKitBitsSaved is not None:
fn = Draw._DrawRDKitBitsSaved
else:
fn = Draw.DrawRDKitBits
return _DrawBit(fn, *args, drawOptions=drawOptions, **kwargs)
DrawRDKitBits.__doc__ = Draw.DrawRDKitBits.__doc__
_rendererInstalled = False
def EnableSubstructMatchRendering():
if not hasattr(rdchem.Mol, '__GetSubstructMatch'):
rdchem.Mol.__GetSubstructMatch = rdchem.Mol.GetSubstructMatch
@@ -372,13 +420,18 @@ def EnableSubstructMatchRendering():
_methodsToDelete = []
_rendererInstalled = False
def InstallIPythonRenderer():
global _MolsToGridImageSaved, _DrawRDKitBitSaved, _DrawRDKitBitsSaved, _DrawMorganBitSaved, _DrawMorganBitsSaved
global _rendererInstalled
if _rendererInstalled:
return
# in case of a reload, there may be some cleanup necessary in other modules that we have
# monkey patched. So go ahead and do an uninstall to cleanup before we start. This was
# Github #8082
UninstallIPythonRenderer()
rdchem.Mol._repr_png_ = _toPNG
rdchem.Mol._repr_svg_ = _toSVG
_methodsToDelete.append((rdchem.Mol, '_repr_png_'))
@@ -399,24 +452,27 @@ def InstallIPythonRenderer():
EnableSubstructMatchRendering()
Image.Image._repr_png_ = display_pil_image
_methodsToDelete.append((Image.Image, '_repr_png_'))
_MolsToGridImageSaved = Draw.MolsToGridImage
Draw._MolsToGridImageSaved = Draw.MolsToGridImage
Draw.MolsToGridImage = ShowMols
_DrawRDKitBitSaved = Draw.DrawRDKitBit
Draw.MolsToGridImage.__doc__ = Draw._MolsToGridImageSaved.__doc__
Draw._DrawRDKitBitSaved = Draw.DrawRDKitBit
Draw.DrawRDKitBit = DrawRDKitBit
_DrawRDKitBitsSaved = Draw.DrawRDKitBits
Draw.DrawRDKitBit.__doc__ = Draw._DrawRDKitBitSaved.__doc__
Draw._DrawRDKitBitsSaved = Draw.DrawRDKitBits
Draw.DrawRDKitBits = DrawRDKitBits
_DrawMorganBitSaved = Draw.DrawMorganBit
Draw.DrawRDKitBits.__doc__ = Draw._DrawRDKitBitsSaved.__doc__
Draw._DrawMorganBitSaved = Draw.DrawMorganBit
Draw.DrawMorganBit = DrawMorganBit
_DrawMorganBitsSaved = Draw.DrawMorganBits
Draw.DrawMorganBit.__doc__ = Draw.DrawMorganBit.__doc__
Draw._DrawMorganBitsSaved = Draw.DrawMorganBits
Draw.DrawMorganBits = DrawMorganBits
rdchem.Mol.__DebugMol = rdchem.Mol.Debug
rdchem.Mol.Debug = lambda self, useStdout=False: self.__DebugMol(useStdout=useStdout)
Draw.DrawMorganBits.__doc__ = Draw._DrawMorganBitsSaved.__doc__
if not hasattr(rdchem.Mol, '__DebugMol'):
rdchem.Mol.__DebugMol = rdchem.Mol.Debug
rdchem.Mol.Debug = lambda self, useStdout=False: self.__DebugMol(useStdout=useStdout)
_rendererInstalled = True
InstallIPythonRenderer()
def DisableSubstructMatchRendering():
if hasattr(rdchem.Mol, '__GetSubstructMatch'):
rdchem.Mol.GetSubstructMatch = rdchem.Mol.__GetSubstructMatch
@@ -427,27 +483,37 @@ def DisableSubstructMatchRendering():
def UninstallIPythonRenderer():
global _MolsToGridImageSaved, _DrawRDKitBitSaved, _DrawMorganBitSaved, _DrawMorganBitsSaved
global _rendererInstalled, _methodsToDelete
if not _rendererInstalled:
return
for cls, attr in _methodsToDelete:
delattr(cls, attr)
if hasattr(cls,attr):
delattr(cls, attr)
_methodsToDelete = []
DisableSubstructMatchRendering()
if _MolsToGridImageSaved is not None:
Draw.MolsToGridImage = _MolsToGridImageSaved
if _DrawRDKitBitSaved is not None:
Draw.DrawRDKitBit = _DrawRDKitBitSaved
if _DrawRDKitBitsSaved is not None:
Draw.DrawRDKitBits = _DrawRDKitBitsSaved
if _DrawMorganBitSaved is not None:
Draw.DrawMorganBit = _DrawMorganBitSaved
if _DrawMorganBitsSaved is not None:
Draw.DrawMorganBits = _DrawMorganBitsSaved
if hasattr(Draw,'_MolsToGridImageSaved') and Draw._MolsToGridImageSaved is not None:
Draw.MolsToGridImage = Draw._MolsToGridImageSaved
del Draw._MolsToGridImageSaved
if hasattr(Draw,'_DrawRDKitBitSaved') and Draw._DrawRDKitBitSaved is not None:
Draw.DrawRDKitBit = Draw._DrawRDKitBitSaved
del Draw._DrawRDKitBitSaved
if hasattr(Draw,'_DrawRDKitBitsSaved') and Draw._DrawRDKitBitsSaved is not None:
Draw.DrawRDKitBits = Draw._DrawRDKitBitsSaved
del Draw._DrawRDKitBitsSaved
if hasattr(Draw,'_DrawMorganBitSaved') and Draw._DrawMorganBitSaved is not None:
Draw.DrawMorganBit = Draw._DrawMorganBitSaved
del Draw._DrawMorganBitSaved
if hasattr(Draw,'_DrawMorganBitsSaved') and Draw._DrawMorganBitsSaved is not None:
Draw.DrawMorganBits = Draw._DrawMorganBitsSaved
del Draw._DrawMorganBitsSaved
if hasattr(rdchem.Mol, '__DebugMol'):
rdchem.Mol.Debug = rdchem.Mol.__DebugMol
del rdchem.Mol.__DebugMol
_rendererInstalled = False
InstallIPythonRenderer()