资讯动态

RDKit MolDraw2DCairo:用代码批量绘制高质量分子结构图

发布时间:2026/10/16 15:13:19 来源:尧图企业网站定制
做化学信息学的人绝大多数都遇到过这个需求手头一排SMILES要变成结构图放进报告、PPT或者论文里。以前我习惯用可视化软件一个个导出遇到几十上百个分子就复制粘贴到手软改个样式还得重来一遍。后来在RDKit里摸到MolDraw2DCairo模块问题一下子简单了——写一个循环所有分子的PNG直接落盘分辨率、键宽、原子序号、高亮色统统用代码控制。这篇文章就从实际使用角度聊聊MolDraw2DCairo的用法、参数调整和踩过的坑给同样被画图折磨过的人一个能直接抄作业的参考。这个模块适合谁做药物化学、计算化学、材料化学的同学以及任何需要把分子结构批量转成图片的人。如果你是刚接触RDKit的新手跟着文章跑通第一张图再去看API文档会轻松很多如果你已经画过图但觉得不够精细第二部分到第四部分的进阶参数和批量方案应该能帮上忙。1. 为什么画分子结构我首选MolDraw2DCairo1.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 python3.11 conda activate chemdraw conda install -c conda-forge rdkit如果你是pip用户试着装一下rdkit之后再检查import rdkit.Chem.Draw.rdMolDraw2D能不能正常导入。如果报错就需要根据你的操作系统安装cairo库Linux一般叫libcairo2-devmacOS用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())这段代码做了五件事对应五个关键步骤用SMILES解析分子对象创建一个400x300像素的Cairo绘图画布把分子画到画布上调用FinishDrawing()结束绘制取出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)OC1CCCCC1C(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(COC1CC(C(CC1)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, highlightAtomshighlight_atoms, highlightBondshighlight_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][N1: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(foutput/{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(datad2d.GetDrawingText()))这样在Notebook里浏览结果非常快配合pandas切片可以一边看表格数据一边看对应结构图。我做化合物库筛选时经常用这个方式把活性数据和结构图放在同一个界面里检查比一会儿切到文件管理器高效得多。4.3 把绘图封装成工具函数一个参数控制全局画图这件事重复性很强我的建议是封装一个统一入口把所有参数收拢到一份配置里。这样后面画一百张图和画一张图调用方式完全一致。def draw_mol_to_png( mol, pathNone, width600, height400, highlight_atomsNone, highlight_bondsNone, fixed_bond_length25, bond_line_width2.5, font_size18, ): 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, highlightAtomshighlight_atoms, highlightBondshighlight_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, pathffigs/{cid}.png, highlight_atomskey_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给了我们非常精细的控制能力真正用起来之后你会发现分子结构图的质量其实是由代码里那些不起眼的参数决定的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑