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.
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?
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:
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: