Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RDKit image MolToImage scales Bond widths and Element Labels inconsistently for different image sizes?

I've noticed that when I create an image from a molecule in RDKit, the size argument leads to inconsistent scaling of the bond width and element labels. The bigger the size, the thinner the lines and the smaller the element labels.

I've run a test by generating an image for the same molecule using MolToImage at progressively bigger sizes. I rescaled those images to size=(600,600) and then concatenated them into a GIF. This is the result.

Resulting GIF

Here's my code

from glob import glob
from rdkit import Chem
from rdkit.Chem import Draw
from PIL import Image,ImageDraw,ImageFont


def make_frames_from_smi(smi):

    for i in range(10):
        s = (i+3)*100
        mol = Chem.MolFromSmiles(smi)
        img = Draw.MolToImage(mol,size=(s,s))
        img = img.resize((600,600))
        draw = ImageDraw.Draw(img)

        text = '%d: Initial Size: (%d,%d)'%(i+1,s,s)

        font_size = 40
        font = ImageFont.truetype("arial.ttf", font_size)  # Use your desired font


        # Calculate text position
        image_width, image_height = img.size
        text_x = (image_width - (bbox[2] - bbox[0])) // 2
        text_y = 20  # Adjust the vertical position as needed

        draw.text((text_x, text_y), text, font=font, fill='black')
        img.save('%03dtest.png'%i)


def make_gif_from_frames(paths):

    frames_paths = glob(paths)
    frames = [Image.open(imgp) for imgp in frames_paths]

    frames[0].save("mols.gif", format="GIF", append_images=frames, save_all=True, duration=500, loop=False)



# make RDKit mol obj.
smi = 'CN(C)CC1CCCCC1(C2=CC(=CC=C2)OC)O'
make_frames_from_smi(smi)
make_gif_from_frames('*.png')

Is this expected behaviour? Is the bond width held constant for a certain absolute value of pixels? How can I generate these images with consistent proportions regardless of width/height of pixels?

like image 640
J.Doe Avatar asked Feb 02 '26 19:02

J.Doe


1 Answers

OK found , I hope , two solutions ???

First one, using Draw.rdMolDraw2D.MolDraw2DCairo:

from glob import glob
from rdkit import Chem
from rdkit.Chem import Draw
from PIL import Image


from io import BytesIO

def make_frames_from_smi(smi):

    mol = Chem.MolFromSmiles(smi)

    for i in range(10):
        s = (i+3)*100
        mol = Chem.MolFromSmiles(smi)
        
        d = Draw.rdMolDraw2D.MolDraw2DCairo(s,s)
        
        dopts = d.drawOptions()
        
        # dopts.maxFontSize=40
        # dopts.minFontSize=40
        
        dopts.maxFontSize=int(0.13*s)
        dopts.minFontSize=int(0.13*s)
        
        
        print('dopts.bondLineWidth : ', dopts.bondLineWidth) #default is 2.0
        
        dopts.bondLineWidth=(0.007*s)
        
        print('dopts.bondLineWidth : ', dopts.bondLineWidth) #default is 2.0
    
    
        d.DrawMolecule(mol, legend= '%d: Initial Size: (%d,%d)'%(i+1,s,s))
        
        d.FinishDrawing()

        
        img = d.GetDrawingText()
        
        img_b = BytesIO()
    
        img_b.write(img)
        
        
        pil_img = Image.open(img_b).resize((600,600))
        
        pil_img.save('%03dtest.png'%i)
        
        


def make_gif_from_frames(paths):

    frames_paths = glob(paths)
    
    frames_paths.sort()
    
    print(frames_paths)
    
        
    frames = [Image.open(imgp) for imgp in frames_paths]
    
    frames[0].save("mols.gif", format="GIF", append_images=frames, save_all=True, duration=500, loop=False)


smi = 'CN(C)CC1CCCCC1(C2=CC(=CC=C2)OC)O'
make_frames_from_smi(smi)
make_gif_from_frames('*.png')

result:

enter image description here

or using SVG, Draw.rdMolDraw2D.MolDraw2DSVG, that I like more as a pic format :

import rdkit

print('\n-------------------------------')
print('\n rdkit Version : ', rdkit.__version__)
print('\n-------------------------------')

from glob import glob
from rdkit import Chem
from rdkit.Chem import Draw
from PIL import Image,ImageDraw,ImageFont

from io import BytesIO
from cairosvg import svg2png

from moviepy.editor import ImageClip, concatenate_videoclips

def make_frames_from_smi(smi):

    mol = Chem.MolFromSmiles(smi)

    drawer= Draw.rdMolDraw2D.MolDraw2DSVG(600,600)
    
    dopts = drawer.drawOptions()
    
    for i in dir(dopts) :
        
        print(i)
        
    dopts.minFontSize = -1
    dopts.maxFontSize = -1
        
    # dopts.minFontSize = 80
    # dopts.maxFontSize = 80
    
    print('drawer.FontSize : ', drawer.FontSize())
          
    # dopts.annotationFontScale = 0.5
    
    dopts.addAtomIndices = True
        
    drawer.DrawMolecule(mol)

        
    drawer.FinishDrawing()

    svg_data = drawer.GetDrawingText()


        
    with open('dtest.svg' , 'w') as handler:
            
            handler.write(svg_data)
    
    for i in range(10):
        s = (i+3)*100
        
        png = svg2png(bytestring=svg_data)

        img = Image.open(BytesIO(png)).convert('RGBA').resize((s,s))

        img.save('%03dtest.png'%i)


def make_gif_from_frames(paths):
    
    input_png_list = glob(paths)
    input_png_list.sort()
    
    
    clips = [ImageClip(i).set_duration(1)
                  for i in input_png_list]
    concat_clip = concatenate_videoclips(clips, method="compose")
    concat_clip.write_gif("test.gif", fps=2)    
    

smi = 'CN(C)CC1CCCCC1(C2=CC(=CC=C2)OC)O'
make_frames_from_smi(smi)
make_gif_from_frames('*.png')

result:

enter image description here

like image 194
pippo1980 Avatar answered Feb 04 '26 07:02

pippo1980



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!