pythonrdkit

How to change highlight color using MolsToGridImage in RDKit using python?


The cheminformatics RDKit tool features the RDKit Cookbook with example code for a range of applications. One of them is to Highlight the Differences between 2 Molecules, here by using fMCS to map similarities, then marking those residues which are not shared. This example works well, the code is here

I need the change the highlight color, but cannot get it to work. Based on the code link above, I've tried so far:

from rdkit import Chem
from rdkit.Chem import Draw
from rdkit.Chem.Draw import rdMolDraw2D
from rdkit.Chem.Draw import IPythonConsole
from rdkit.Chem import rdFMCS
from rdkit.Chem.Draw import rdDepictor
rdDepictor.SetPreferCoordGen(True)
IPythonConsole.drawOptions.minFontSize=20
IPythonConsole.drawOptions.setHighlightColour=(0,1,0)
from collections import defaultdict

This had no effect at all, pic was drawn with default highlight.

d2g = Draw.MolsToGridImage([mol1, mol2]) 
opts = d2g.drawOptions()
opts.setHighlightColour = (0,1,0)
d2g([mol1, mol2],highlightAtomLists=[target_atm1, target_atm2])

This throws the error

AttributeError: 'Image' object has no attribute 'drawOptions'

Then I got it to work with this one:

from rdkit import Chem
from rdkit.Chem import Draw
from rdkit.Chem.Draw import rdMolDraw2D
from rdkit.Chem.Draw import IPythonConsole
from rdkit.Chem import rdFMCS
from rdkit.Chem.Draw import rdDepictor
rdDepictor.SetPreferCoordGen(True)
IPythonConsole.drawOptions.minFontSize=20
from collections import defaultdict

mol1 = Chem.MolFromSmiles('FC1=CC=C2C(=C1)C=NN2')
mol2 = Chem.MolFromSmiles('CCC1=C2NN=CC2=CC(Cl)=C1')

from PIL import Image
from io import BytesIO
def show_mol(d2d,mol,legend='',highlightAtoms=[]):
    d2d.DrawMolecule(mol,legend=legend, highlightAtoms=highlightAtoms)
    d2d.FinishDrawing()
    bio = BytesIO(d2d.GetDrawingText())
    return Image.open(bio)
def show_images(imgs,buffer=5):
    height = 0
    width = 0
    for img in imgs:
        height = max(height,img.height)
        width += img.width
    width += buffer*(len(imgs)-1)
    res = Image.new("RGBA",(width,height))
    x = 0
    for img in imgs:
        res.paste(img,(x,0))
        x += img.width + buffer
    return res

mcs = rdFMCS.FindMCS([mol1,mol2])
mcs_mol = Chem.MolFromSmarts(mcs.smartsString)
match1 = mol1.GetSubstructMatch(mcs_mol)
target_atm1 = []
for atom in mol1.GetAtoms():
        if atom.GetIdx() not in match1:
            target_atm1.append(atom.GetIdx())
match2 = mol2.GetSubstructMatch(mcs_mol)
target_atm2 = []
for atom in mol2.GetAtoms():
        if atom.GetIdx() not in match2:
            target_atm2.append(atom.GetIdx())

img1 = []
d2dx = Draw.MolDraw2DCairo(350,300)
doptsx = d2dx.drawOptions()
doptsx.setHighlightColour((1,0.84,0,.5))
img1.append(show_mol(d2dx,mol1,highlightAtoms=target_atm1))
d2dx = Draw.MolDraw2DCairo(350,300)
doptsx = d2dx.drawOptions()
doptsx.setHighlightColour((1,0.84,0,.5))
img1.append(show_mol(d2dx,mol2, highlightAtoms=target_atm2))
pic = show_images(img1)
pic.save('hallo.png')
pic

Problems are, 1) I need an svg output and, 2) if the molecules have different size or alternate SMILE config is used, only MolsToGrid places them properly adjacent within same subpanel boundaries. So I need to use MolsToGrid for scientific publication-quality images.

I am a complete python n00b using jupyter lab on a windows 11 PC.

Python 3.8.18 (default, Sep 11 2023, 13:39:12) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32


Solution

  • I was not able to figure out how to set HighlightAtomColors in MolToGridImage

    I resorted to:

    import rdkit
    print('\n-------------------------------')
    print('\n rdkit Version : ', rdkit.__version__)
    print('\n-------------------------------')
    
    from rdkit import Chem
    from rdkit.Chem import Draw
    from rdkit.Chem import rdFMCS
    
    from rdkit.Chem.Draw import rdDepictor
    rdDepictor.SetPreferCoordGen(True)
    
    # rdDepictor.SetPreferCoordGen(False) ## molecules in final pics  are not mirrored
    
    from io import BytesIO, StringIO
    from IPython.display import SVG, display
    
    smi1 = 'FC1=CC=C2C(=C1)C=NN2'
    smi2 = 'CCC1=C2NN=CC2=CC(Cl)=C1'
    
    print(smi1)
    print(smi2)
    
    mol1 = Chem.MolFromSmiles(smi1)
    mol2 = Chem.MolFromSmiles(smi2)
    
    smi_mol1 = Chem.MolToSmiles(mol1)
    smi_mol2 = Chem.MolToSmiles(mol2)
    
    print(smi_mol1)
    print(smi_mol2)
    
    def show_mol(d2d,mol,legend='',highlightAtoms=[]):
        d2d.DrawMolecule(mol,legend=legend, highlightAtoms=highlightAtoms)
        d2d.FinishDrawing()
       
        svg = d2d.GetDrawingText()
        img = BytesIO(svg.encode())
        
        # print(img.read()) #  prints SVG file content (tex file in XML markup language)
        # display(SVG(img)  # should show the image inside a jupyter notebook, check to be sure it does
        # img.seek(0)
        
        return img  
    
    mcs = rdFMCS.FindMCS([mol1,mol2])
    mcs_mol = Chem.MolFromSmarts(mcs.smartsString)
    match1 = mol1.GetSubstructMatch(mcs_mol)
    target_atm1 = []
    for atom in mol1.GetAtoms():
            if atom.GetIdx() not in match1:
                target_atm1.append(atom.GetIdx())
    match2 = mol2.GetSubstructMatch(mcs_mol)
    target_atm2 = []
    for atom in mol2.GetAtoms():
            if atom.GetIdx() not in match2:
                target_atm2.append(atom.GetIdx())
    
    img1 = []
    d2dx = Draw.MolDraw2DSVG(350,300)
    doptsx = d2dx.drawOptions()
    doptsx.setHighlightColour((1,0.84,0,.5))
    doptsx.bgColor=None
    img1.append(show_mol(d2dx,mol1,highlightAtoms=target_atm1))
    
    d2dx = Draw.MolDraw2DSVG(350,300)
    doptsx = d2dx.drawOptions()
    doptsx.setHighlightColour((0,0.84,0,.5))
    doptsx.bgColor=None
    img1.append(show_mol(d2dx,mol2, highlightAtoms=target_atm2))
    
    #### https://stackoverflow.com/questions/61800296/python-merging-2-svg-elelments-into-one-svg-element
    import io
    
    from svglib.svglib import svg2rlg
    
    from reportlab.graphics.shapes import Drawing
    from reportlab.graphics.renderSVG import SVGCanvas, draw
    
    background_content="""<?xml version="1.0" encoding="UTF-8" ?>
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="700" height="300">
    </svg>"""
    
    svg_1_content = img1[0]
    print('svg_1_content : ', svg_1_content , type(svg_1_content))
    
    svg_2_content = img1[1]
    svg_1_element = svg2rlg(svg_1_content)
    
    print('svg_1_element : ' , svg_1_element,  svg_1_element.width , svg_1_element.height)
    
    svg_2_element = svg2rlg(svg_2_content)
    print('svg_2_element : ' , svg_2_element,   svg_2_element.width , svg_2_element.height)
    
    width = 350
    height = 300
    
    svg_element = svg2rlg(io.BytesIO(background_content.encode()))
    
    print('svg_element :', svg_element, svg_element.width , svg_element.height)
    
    d = Drawing(width, height) # setting the width and height
    
    print('d :' , d , d.width)
    
    print('svg_1_element : ' , svg_1_element,  svg_1_element.width , svg_1_element.height)
    print('svg_2_element : ' , svg_2_element,   svg_2_element.width , svg_2_element.height)
    
    d.add(svg_1_element)
    
    d2 = Drawing(width, height)
    
    d2.add(svg_2_element)
    
    d2.translate(350,0)
    
    
    s = StringIO()
    
    c = SVGCanvas((2*width, height))
    
    draw(d,c)
    draw(d2,c)
    
    c.save(s)
    # print(s.getvalue())
    
    with open('output.svg' , 'w')  as handler:
        s.seek(0)
        handler.write(s.read())
        
    # for i in dir(d):
    #     print(i)
        
    # print(help(Drawing))
    # print(help(SVGCanvas))
    
    

    Need more libraries: svglib , reportlab .

    Output *** it is a SVG picture, here it is an uploaded .png copy:

    enter image description here

    About the problem of the different image scales, maybe could be fixed with following DRAWING options:

    d2dx = Draw.MolDraw2DSVG(350,300)
    doptsx = d2dx.drawOptions()
    doptsx.setHighlightColour((0,0.84,0,.5))
    doptsx.bgColor=None
    doptsx.maxFontSize=20      #   <-----
    doptsx.minFontSize=20      #   <-----
    doptsx.fixedBondLength=40  #   <-----
    

    In code:

    import rdkit
    print('\n-------------------------------')
    print('\n rdkit Version : ', rdkit.__version__)
    print('\n-------------------------------')
    
    from io import BytesIO, StringIO
    
    from rdkit import Chem
    from rdkit.Chem import Draw
    from rdkit.Chem.Draw import rdMolDraw2D
    from rdkit.Chem.Draw import IPythonConsole
    from rdkit.Chem import rdFMCS
    from rdkit.Chem.Draw import rdDepictor
    rdDepictor.SetPreferCoordGen(True)
    IPythonConsole.drawOptions.minFontSize=20
    from collections import defaultdict
    
    mol1 = Chem.MolFromSmiles('C12=CC(O)=CC=C1C1=CC=C(O)C=C1C(=O)O2')
    mol2 = Chem.MolFromSmiles('C12=CC(O)=CC=C1C(=O)OC1C=C(O)C=CC=12') #mol2_1
    
    # mol2 = Chem.MolFromSmiles('C1=CC2=C(C=C1O)C3=C(C=C(C=C3)O)OC2=O') #mol2_2
    
    def show_mol(d2d,mol,legend='',highlightAtoms=[]):
        d2d.DrawMolecule(mol,legend=legend, highlightAtoms=highlightAtoms)
        d2d.FinishDrawing()
       
        svg = d2d.GetDrawingText()
        img = BytesIO(svg.encode())
    
        
        return img  
    
    mcs = rdFMCS.FindMCS([mol1,mol2])
    mcs_mol = Chem.MolFromSmarts(mcs.smartsString)
    match1 = mol1.GetSubstructMatch(mcs_mol)
    target_atm1 = []
    for atom in mol1.GetAtoms():
            if atom.GetIdx() not in match1:
                target_atm1.append(atom.GetIdx())
    match2 = mol2.GetSubstructMatch(mcs_mol)
    target_atm2 = []
    for atom in mol2.GetAtoms():
            if atom.GetIdx() not in match2:
                target_atm2.append(atom.GetIdx())
    
    img1 = []
    d2dx = Draw.MolDraw2DSVG(350,300)
    doptsx = d2dx.drawOptions()
    doptsx.setHighlightColour((1,0.84,0,.5))
    doptsx.bgColor=None
    
    doptsx.maxFontSize=20
    doptsx.minFontSize=20
    doptsx.fixedBondLength=40
    
    img1.append(show_mol(d2dx,(mol1),highlightAtoms=target_atm1))
    
    d2dx = Draw.MolDraw2DSVG(350,300)
    doptsx = d2dx.drawOptions()
    doptsx.setHighlightColour((0,0.84,0,.5))
    doptsx.bgColor=None
    
    doptsx.maxFontSize=20
    doptsx.minFontSize=20
    doptsx.fixedBondLength=40
    
    img1.append(show_mol(d2dx,mol2, highlightAtoms=target_atm2))
    
    #### https://stackoverflow.com/questions/61800296/python-merging-2-svg-elelments-into-one-svg-element
    import io
    
    from svglib.svglib import svg2rlg
    
    from reportlab.graphics.shapes import Drawing
    from reportlab.graphics.renderSVG import SVGCanvas, draw
    
    
    background_content="""<?xml version="1.0" encoding="UTF-8" ?>
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="700" height="300">
    </svg>"""
    
    
    svg_1_content = img1[0]
    print('svg_1_content : ', svg_1_content , type(svg_1_content))
    
    svg_2_content = img1[1]
    svg_1_element = svg2rlg(svg_1_content)
    
    print('svg_1_element : ' , svg_1_element,  svg_1_element.width , svg_1_element.height)
    
    svg_2_element = svg2rlg(svg_2_content)
    
    print('svg_2_element : ' , svg_2_element,   svg_2_element.width , svg_2_element.height)
    
    width = 350
    height = 300
    
    svg_element = svg2rlg(io.BytesIO(background_content.encode()))
    
    print('svg_element :', svg_element, svg_element.width , svg_element.height)
    
    d = Drawing(width, height) # setting the width and height
    
    print('d :' , d , d.width)
    
    print('svg_1_element : ' , svg_1_element,  svg_1_element.width , svg_1_element.height)
    print('svg_2_element : ' , svg_2_element,   svg_2_element.width , svg_2_element.height)
    
    d.add(svg_1_element)
    
    d2 = Drawing(width, height)
    
    d2.add(svg_2_element)
    
    d2.translate(350,0)
    
    s = StringIO()
    c = SVGCanvas((2*width, height))
    
    draw(d,c, )   
    draw(d2,c, ) 
    
    c.save(s)
    # print(s.getvalue())
    
    with open('output_mol1_mol2_1.svg' , 'w')  as handler:   
        s.seek(0)
        handler.write(s.read())
    

    Output with options above set:

    enter image description here

    With options (#<----) commented out:

    enter image description here

    Orientation problem remains, see output using mol2_1 (in code above):

    enter image description here

    Using:

    mol1 = Chem.MolFromSmiles('C12=CC(O)=CC=C1C1=CC=C(O)C=C1C(=O)O2')
    mol2 = Chem.MolFromSmiles('C1=CC2=C(C=C1O)C3=C(C=C(C=C3)O)OC2=O') #mol2_2
    
    smi_mol1 = Chem.MolToSmiles(mol1, canonical=False)
    smi_mol2 = Chem.MolToSmiles(mol2, canonical=False)
    
    smi_mol1 = Chem.MolToSmiles(mol1, canonical=True)
    smi_mol2 = Chem.MolToSmiles(mol2, canonical=True)
    
    print(smi_mol1)
    print(smi_mol2)
    
    mol1 = Chem.MolFromSmiles(smi_mol1)
    mol2 = Chem.MolFromSmiles(smi_mol2)
    

    At the beginning of my code I get as output SMILES:

    O=c1oc2cc(O)ccc2c2ccc(O)cc12
    O=c1oc2cc(O)ccc2c2cc(O)ccc12
    

    That feed to my script give picture:

    enter image description here