☰
RDKit MolDraw2DCairo:用代码批量绘制高质量分子结构图
2026/10/15 10:49:53 网站建设 项目流程

做化学信息学的人,绝大多数都遇到过这个需求:手头一排SMILES,要变成结构图放进报告、PPT或者论文里。以前我习惯用可视化软件一个个导出,遇到几十上百个分子就复制粘贴到手软,改个样式还得重来一遍。后来在RDKit里摸到MolDraw2DCairo模块,问题一下子简单了——写一个循环,所有分子的PNG直接落盘,分辨率、键宽、原子序号、高亮色统统用代码控制。这篇文章就从实际使用角度,聊聊MolDraw2DCairo的用法、参数调整和踩过的坑,给同样被画图折磨过的人一个能直接抄作业的参考。

这个模块适合谁?做药物化学、计算化学、材料化学的同学,以及任何需要把分子结构批量转成图片的人。如果你是刚接触RDKit的新手,跟着文章跑通第一张图,再去看API文档会轻松很多;如果你已经画过图但觉得不够精细,第二部分到第四部分的进阶参数和批量方案应该能帮上忙。

1. 为什么画分子结构我首选MolDraw2DCairo

1.1 一个核心问题:同样是画图,为什么要单独选Cairo后端

RDKit的绘图能力并不是只有一条路。很多人最早接触的是Draw.MolToImage,传入一个分子对象就能拿到PIL的图片,简单是简单,但一旦需要精细控制——比如给原子标序号、高亮某个片断、控制键长键宽、输出透明背景——就有点使不上劲。MolDraw2DCairo走的是另一条路:它是RDKit独立渲染引擎的Cairo后端,直接输出高质量光栅图,所有绘制细节都暴露在drawOptions里,想要什么效果基本都能改。

Cairo后端还有一个优势是抗锯齿做得很好。生成的PNG放大之后边缘不会出现明显的锯齿,尤其把图缩到很小放进表格里时,结构式仍然能保持可读性。这个细节在做批量报告时非常关键,因为审阅者看的往往不是原图,而是插进Word或PDF里缩放后的版本。

1.2 MolDraw2D家族的分工:别只盯着一个类用

RDKit绘图模块里,MolDraw2D是一个基类,下面挂了多个具体后端:

后端类输出格式典型使用场景
MolDraw2DCairoPNG(光栅图)论文插图、报告打包、批量出图
MolDraw2DSVGSVG(矢量图)网页展示、可交互文档
MolDraw2DFrameBuffer内存中的光栅缓冲配合GUI或动态绘制
MolDraw2DUtils工具函数集合预处理分子、自动排版坐标

实际工作中,我的习惯是:要发表级矢量图就选SVG,要快速落盘PNG就选Cairo。两者在drawOptions上几乎完全一致,学会了Cairo的用法,切到SVG只是把类名换一下而已。这篇文章主要讲Cairo,但所有参数设置对SVG同样适用。

提示:MolDraw2DUtils不是一个绘制后端,而是一个工具类,里面很多函数会在绘制前帮我们处理坐标和加氢,属于"保姆级"辅助,后面会专门说。

2. 环境准备:先把Cairo库和RDKit配对

2.1 安装方式决定你省不省心

RDKit的安装,我强烈建议用conda,而不是裸pip。原因是RDKit的Cairo后端依赖系统层面的Cairo库,conda安装会把libcairo一起打包进来,用户层面完全无感知;如果只用pip装rdkit,后续很可能遇到libcairo.so.2 not found这类报错,还得自己去补系统依赖,纯给自己加戏。

conda create -n chemdraw python=3.11 conda activate chemdraw conda install -c conda-forge rdkit

如果你是pip用户,试着装一下rdkit之后,再检查import rdkit.Chem.Draw.rdMolDraw2D能不能正常导入。如果报错,就需要根据你的操作系统安装cairo库,Linux一般叫libcairo2-dev,macOS用brew install cairo。这一步卡住的人其实不少,后面问题排查部分会展开说。

2.2 最小可运行示例:五步生成第一张PNG

先跑一个最简单的例子,把乙醇画出来。

from rdkit import Chem from rdkit.Chem.Draw import rdMolDraw2D mol = Chem.MolFromSmiles("CCO") d2d = rdMolDraw2D.MolDraw2DCairo(400, 300) d2d.DrawMolecule(mol) d2d.FinishDrawing() with open("ethanol.png", "wb") as f: f.write(d2d.GetDrawingText())

这段代码做了五件事,对应五个关键步骤:

  1. 用SMILES解析分子对象;
  2. 创建一个400x300像素的Cairo绘图画布;
  3. 把分子画到画布上;
  4. 调用FinishDrawing()结束绘制;
  5. 取出PNG字节流写入文件。

这里有两个细节值得注意。第一,FinishDrawing()必须被调用,否则GetDrawingText()拿不到数据,这不是可选项,而是绘制流程的收尾动作。第二,GetDrawingText()返回的是bytes,不是文件路径,所以写入文件时要使用二进制模式"wb",别写成文本模式。

2.3 画图前先理解drawOptions:一个属性定全局

MolDraw2DCairo创建之后,真正决定图片长什么样的是drawOptions()返回的那个配置对象。它上面有几十个属性,从背景色、边距、字体大小到键宽、高亮色,全部在这个地方调整。

d2d.drawOptions().bgColor = (1, 1, 1) # 背景白色,默认就是 d2d.drawOptions().bondLineWidth = 2 # 键宽 d2d.drawOptions().fontSize = 18 # 原子符号字号 d2d.drawOptions().padding = 0.15 # 画布边距比例

理解这个配置对象的机制非常关键。因为drawOptions是引用同一个配置实例,你在绘制前改多少遍都有效,但一旦FinishDrawing()之后再去改,已经生成的图不会重新渲染。所以最佳实践是:先把所有参数设置好,再调用DrawMolecule,最后FinishDrawing。

注意:不同RDKit版本里drawOptions的属性名可能有细微差异,比如早期版本叫bondLineWidth,后来有人习惯写strokeWidth,实际以当前版本的属性名为准。跑之前先dir(d2d.drawOptions())看一眼最保险。

3. 进阶绘制:从好看变成能用

3.1 给原子加序号:图谱解析不再靠猜

画分子结构时,经常需要标注原子编号,尤其是做核磁归属、质谱碎片解析时,编号能让沟通效率翻倍。实现方式有几种,我常用的是原子映射序号配合绘制参数:

from rdkit import Chem from rdkit.Chem.Draw import rdMolDraw2D mol = Chem.MolFromSmiles("CC(=O)OC1=CC=CC=C1C(=O)O") for atom in mol.GetAtoms(): atom.SetAtomMapNum(atom.GetIdx()) d2d = rdMolDraw2D.MolDraw2DCairo(600, 400) d2d.drawOptions().fontSize = 16 d2d.DrawMolecule(mol) d2d.FinishDrawing() with open("aspirin_labeled.png", "wb") as f: f.write(d2d.GetDrawingText())

这段代码里,SetAtomMapNum把每个原子的索引写进原子映射编号,绘制时RDKit会把这个编号渲染成原子符号旁边的数字,效果类似于质谱或核磁文献里常见的那种标注格式。

如果你只是想让每个原子显示它在分子里的顺序索引,也有一个更省事的选项:d2d.drawOptions().addAtomIndices = True。两者的区别在于,addAtomIndices直接输出原子在分子图中的位置序号;而SetAtomMapNum走的是原子标签渲染管线,显示的是设定的编号值,灵活性更高。比如你想按某种自定义排序规则显示编号,或者只给部分原子编号,就优先用后者。

3.2 子结构高亮:一眼定位关键片断

药物化学里经常要把活性基团、代谢位点或者反应中心标出来。MolDraw2DCairo原生支持原子和键的高亮,用法非常直白。

mol = Chem.MolFromSmiles("COC1=CC(=C(C=C1)NC(=O)C)C(F)(F)F") # 假设我们要高亮酰胺键和苯环上某个位置 highlight_atoms = [9, 10, 11, 12] highlight_bonds = [(9, 12)] d2d = rdMolDraw2D.MolDraw2DCairo(500, 400) rdMolDraw2D.MolDraw2DUtils.PrepareAndDrawMolecule( d2d, mol, highlightAtoms=highlight_atoms, highlightBonds=highlight_bonds ) d2d.FinishDrawing()

注意这里用了PrepareAndDrawMolecule,它比直接DrawMolecule多做了一步:先调用PrepareMolForDrawing给分子加氢并生成合理的2D坐标,再执行绘制。这一步对高亮场景特别有用,因为加氢之后原子索引会变化,提前自己算好目标原子索引再传入,比在原始SMILES上拍脑袋猜索引靠谱得多。

高亮颜色默认是黄色,想改也很容易:

d2d.drawOptions().highlightColour = (0.9, 0.4, 0.4) # 淡红色

或者对单个原子单独定制颜色:

highlightAtomColors = { 9: (0.9, 0.8, 0.2), 10: (0.2, 0.7, 0.9) }

这种细粒度控制在画SAR(构效关系)图时非常实用,一块图上不同颜色代表不同性质,观众一眼就能抓到重点信息。

3.3 键长键宽与配色:让结构图达到出版级

很多人用默认参数画完图总觉得不够专业,但说不出差在哪。其实原因基本就是三个:键长太短或太长导致结构松散、键宽过细显得单薄、字体偏小导致缩放后看不清。drawOptions里这三个参数值得花时间调到位。

opts = d2d.drawOptions() opts.fixedBondLength = 25 # 固定键长,单位是像素 opts.bondLineWidth = 2.5 # 键宽 opts.fontSize = 22 # 原子符号字体 opts.padding = 0.1 # 内容与外边框的留白比例

fixedBondLength这个参数很关键。默认情况下,RDKit会结合画布大小自动计算键长,画布变大,键也会变长。但如果你希望同一批分子在不同画布尺寸下保持视觉一致的键长比例,就把它设成一个固定像素值。我一般控制在22到30之间,太大会把结构拉散,太小则容易叠成一团。

配色方面,Cairo后端遵循RDKit的元素配色方案,碳是深灰色、氧是红色、氮是蓝色,这个配色表基本是化学界的行业共识,不建议乱改。但如果要追求完全统一的黑白印刷效果,可以把元素颜色全部改成黑色:

opts.useBWAtomPalette = True

这个参数在论文投稿时非常好用,因为很多期刊对彩色图片有额外费用,黑白结构图反而是最稳妥的选择。

3.4 把反应式也画出来:绘制接口比你想象的宽

MolDraw2DCairo不仅能画单个分子,还能画反应式。只要把反应对象传给绘制函数,箭头、加号、反应物和产物都会被自动排版。

from rdkit import Chem from rdkit.Chem import rdChemReactions from rdkit.Chem.Draw import rdMolDraw2D rxn = rdChemReactions.ReactionFromSmarts( "[C:1][N:2]>>[C:1][N+1:2]" ) d2d = rdMolDraw2D.MolDraw2DCairo(800, 300) rdMolDraw2D.MolDraw2DUtils.PrepareAndDrawMolecule(d2d, rxn) d2d.FinishDrawing() with open("reaction.png", "wb") as f: f.write(d2d.GetDrawingText())

这个能力用来画合成路线爽在什么地方?你不需要手动截取一个个中间产物再拼图,直接把SMARTS反应模式传进去,箭头左右自动对齐,产物会自动加显式氢或者按需显示。尤其做条件筛选时,一套模板代码可以控制成百上千个反应式输出为同一规格的图。

提示:反应式绘图里,PrepareAndDrawMolecule的预处理同样有效,它会对反应物和产物分别加氢、生成2D坐标,最终排版比死板地按SMARTS字符串顺序排列好看很多。如果生成的图里某个分子被截断了,优先检查画布宽度是否够大,反应式一般要比单分子宽不少。

4. 批量出图与工作流集成

4.1 从CSV批量生成结构图:一个循环搞定

真实项目中,极少有人只画一张图。最常见的是CSV里几百个分子,需要一个一个出图,并且文件名和某个编号对应。

import csv from rdkit import Chem from rdkit.Chem.Draw import rdMolDraw2D with open("compounds.csv", "r") as f: reader = csv.DictReader(f) for row in reader: smiles = row["SMILES"] cid = row["ID"] mol = Chem.MolFromSmiles(smiles) if mol is None: continue d2d = rdMolDraw2D.MolDraw2DCairo(600, 400) d2d.drawOptions().fixedBondLength = 25 rdMolDraw2D.MolDraw2DUtils.PrepareAndDrawMolecule(d2d, mol) d2d.FinishDrawing() with open(f"output/{cid}.png", "wb") as out: out.write(d2d.GetDrawingText())

这段代码里有几个容易踩的坑,提前讲清楚。

第一,MolFromSmiles解析失败会返回None,批量处理时必须跳过这些行,否则后续调用会直接空指针报错。第二,千万注意PrepareAndDrawMolecule会直接修改传入的分子对象,它会加上氢原子并改变原子索引。如果后续还需要用原始分子做其他计算,建议先Chem.Mol(mol)复制一个副本传给绘制函数。第三,文件命名尽量用ID而不是SMILES本身,SMILES里含有斜杠、括号等特殊字符,直接作为文件名大概率触发跨平台路径问题。

4.2 Jupyter里直接展示PNG:别再落盘再插入

如果你在Jupyter Notebook里做分析,完全没必要把图片先写到磁盘,再用Image地址加载。GetDrawingText()返回的字节流可以直接被前端渲染:

from IPython.display import Image, display mol = Chem.MolFromSmiles("CCO") d2d = rdMolDraw2D.MolDraw2DCairo(400, 300) rdMolDraw2D.MolDraw2DUtils.PrepareAndDrawMolecule(d2d, mol) d2d.FinishDrawing() display(Image(data=d2d.GetDrawingText()))

这样在Notebook里浏览结果非常快,配合pandas切片,可以一边看表格数据一边看对应结构图。我做化合物库筛选时,经常用这个方式把活性数据和结构图放在同一个界面里检查,比一会儿切到文件管理器高效得多。

4.3 把绘图封装成工具函数:一个参数控制全局

画图这件事重复性很强,我的建议是封装一个统一入口,把所有参数收拢到一份配置里。这样后面画一百张图和画一张图,调用方式完全一致。

def draw_mol_to_png( mol, path=None, width=600, height=400, highlight_atoms=None, highlight_bonds=None, fixed_bond_length=25, bond_line_width=2.5, font_size=18, ): d2d = rdMolDraw2D.MolDraw2DCairo(width, height) opts = d2d.drawOptions() opts.fixedBondLength = fixed_bond_length opts.bondLineWidth = bond_line_width opts.fontSize = font_size rdMolDraw2D.MolDraw2DUtils.PrepareAndDrawMolecule( d2d, mol, highlightAtoms=highlight_atoms, highlightBonds=highlight_bonds, ) d2d.FinishDrawing() png = d2d.GetDrawingText() if path: with open(path, "wb") as f: f.write(png) return png

有了这个函数,实际业务代码里就是:

for cid, mol in mol_dict.items(): draw_mol_to_png(mol, path=f"figs/{cid}.png", highlight_atoms=key_atoms[cid])

可维护性高了不少,哪天想统一把键宽加粗,只需要改一处配置,而不是对着一百处调用点逐个改。

心得:批量出图前务必先单张测试。把同一函数的输出画在同一个尺寸下,批量生成后花五分钟肉眼扫一遍缩略图,比全部生成完了再发现原子重叠、字体过小要省事太多。

5. 常见问题与修复实录

5.1 libcairo.so.2找不到:环境问题占了七成

如果你用pip安装RDKit,画图时第一行import就可能炸出libcairo.so.2: cannot open shared object file。这个报错通常不是RDKit包本身坏了,而是系统缺少Cairo的运行时库。

最快的解决方案:换成conda环境重新安装RDKit,一般十几秒就解决。如果实在要用现有pip环境,分别给Linux装libcairo2-dev、macOS装cairo即可。装完之后记得重启Python进程,动态链接库的加载是一次性的,不重启不会生效。

5.2 生成的PNG一片空白:怪不起Cairo,怪你没正确调用

最常见的新手问题就是画布创建了、分子也解析了,打开PNG却是一张白纸或者只有部分原子。排查顺序如下:

  • 检查FinishDrawing()有没有调用,这是拿数据前的强制收尾;
  • 检查是不是用了GetDrawingText()两次,字节流不是流式的,取一次就没了,重复取会返回空;
  • 确认画布宽高不是0或者负数,之前见过有人把宽高写成比例而不是像素值,结果画出一张空图。

5.3 中文字体支持与自定义标签

MolDraw2DCairo本身对标签的支持主要体现在原子符号上,但如果想在图上显示中文注释,直接塞进CSV或标题里,大概率渲染成方框。原因在于Cairo后端默认字体库不包含中文字形,需要在drawOptions().fontFile里指定一个带中文的TTF字体文件路径。字体路径设置之后,原子标签和标题里的中文才能正常显示。更稳妥的方案是:把需要中文说明的文字放到外部排版软件里处理,RDKit先负责结构图本身,保证元素符号和编号清晰干净。

5.4 结构图歪歪扭扭,原子挤成一团

如果绘制出来的结构图原子重叠或间距不均,多半是因为分子没有经过2D坐标预处理,或者使用了不合理的固定键长值。强烈建议所有绘制都走PrepareAndDrawMolecule,而不是直接DrawMolecule。这个函数内部会调用RDKit的2D坐标生成算法,确保布局干净。如果用了预处理还是挤在一起,把fixedBondLength适当调大,同时把padding调小一些,给结构本身更多空间。

5.5 新老版本API差异:同一段代码报错,先查版本

RDKit的绘图API在近几个版本里有一些变动,比如早期直接用from rdkit.Chem.Draw import MolDraw2DCairo,新版本更推荐从rdkit.Chem.Draw.rdMolDraw2D导入,或者直接用from rdkit.Chem.Draw import rdMolDraw2D访问。如果你在网上下载了一个老脚本跑不通,先看报错是ImportError还是AttributeError,前者优先改导入路径,后者往往和drawOptions的属性名有关。

# 检查版本 from rdkit import RDLogger from rdkit import __version__ print(__version__)

比较新的RDKit版本里,绘制引擎MolDraw2DCairo的API总体是稳定的,但个别选项属性确实做过改名,比如backgroundColour和bgColor在不同版本里可能同时存在。遇到这种情况,用dir(d2d.drawOptions())列出所有属性,逐个确认要比瞎猜快得多。

关于版本差异,还有一点值得单独讲:Chem.Draw.MolToImage这个高层封装,底层并不是Cairo,而是PIL的渲染管道。所以如果你追求的是和MolDraw2DCairo完全一致的渲染效果,别改来改去最后改到MolToImage上,两者虽然都能出PNG,但在原子标签、键长控制、透明背景处理上的行为并不完全一样。Cairo的渲染管线在排版稳定性上更胜一筹,这也是它在批量生成场景里口碑好的原因。

注意:有个使用上的坏习惯是创建画布后反复调整参数,再多次调用DrawMolecule,期望实现叠加绘制。实际上这种做法很容易让输出混乱。正确做法是:一个画布只画一张图或者一个反应式,画完就取数据。如果你发现需要在同一张PNG上叠加两个分子,应该先把两个分子合并成一个RWMol,或者用MolsToGridImage类的网格绘制接口。

最后还是聊点个人体会

用了挺长时间MolDraw2DCairo,最大的感受是这个模块把"画分子"这件事从手工劳动变成了真正的代码工程。以前一到项目结题要整理化合物图,心里就发怵,要导出一百多张结构图还要统一格式,想想就头皮发麻;现在只要维护好一个绘图脚本,参数都集中在同一个配置文件里,全部出图也就几秒钟的事。之后再碰到需要把图插进报告的场景,我习惯顺手把fontSize和bondLineWidth调大一档,因为放屏幕上看可能还行,但一旦打印或者投影,过于纤细的线条会被吞掉细节。这个经验是我被打印稿折磨过一次之后总结出来的,建议你也提前试一张打印样张,再决定最终参数。

另外一个小技巧放在最后送给大家:如果你经常画同一类分子,比如某系列化合物的衍生物,可以维护一个基准分子模板,把常用的高亮位点预先定义成元组列表,每次画图直接从模板里取,再也不用对着原子索引数半天。MolDraw2DCairo给了我们非常精细的控制能力,真正用起来之后,你会发现分子结构图的质量,其实是由代码里那些不起眼的参数决定的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询