化学结构标注与 OCSR 校正
校正 OCSR 输出,按 SMILES 或 SMARTS 标准答案为手绘结构评分,在分子上标记原子,再导出 InChIKey 和 SD 文件。
v2.13.0 新增
chemical_structure 标注方案为标注者提供一个结构编辑器,molecule 显示类型绘制一个小分子,标注者可以在上面选择原子。化学数据集的构建者把这两者配合起来做结构校正。在结构校正中,标注者把从专利或论文插图中识别出的结构与原图对照,修正识别错误的原子和键。化学教育研究者和教师用编辑器收集手绘结构,例如某个化合物名称所描述的结构,Potato 在服务器上把每份绘制结果与标准答案比对评分。审编人员用 molecule 显示类型配合 viewer_selection 方案标记原子,例如反应中心或代谢位点。每个答案存储为一个 molfile,并附上所绘结构的规范 SMILES;chemistry 导出把每位标注者的结构写入一个 CSV 文件和一个 SD 文件。
光学化学结构识别(optical chemical structure recognition,OCSR)把化学结构的图像转换为机器可读的结构。Potato 不运行任何 OCSR 模型,研究载入的是其他工具识别出的结构。SMILES 用一行简短的文本表示一个结构,例如乙酸写作 CC(=O)O。MDL molfile 是化学表格文件(chemical table file)家族中的连接表文本格式,SD 文件包含多个 molfile,每个 molfile 后面跟着带名称的数据字段。InChIKey 是 IUPAC InChI 标识符的 27 字符哈希值,同一结构的两种画法得到相同的 InChIKey。
- 编辑器: OpenChemLib JS 9.25.0(BSD-3-Clause),随 Potato 一起提交。编辑器在画布上绘制,工具栏提供键、环、元素、电荷、立体楔形键、擦除和撤销。画布下方的文本框接受键入或粘贴的 SMILES 字符串或 molfile,这个文本框是用键盘操作编辑器的途径。
- RDKit.js(可选): RDKit 的 JavaScript MinimalLib 2026.3.6(BSD-3-Clause)是一个 7 MB 的 WebAssembly 构建,没有随 Potato 提交。
potato vendor fetch rdkit会下载这个构建,并用 npm 注册表的完整性哈希校验下载内容。有了 RDKit.js,浏览器还会计算 RDKit 规范 SMILES、InChI 和 InChIKey,标出 RDKit 无法规整化(sanitize)的结构,molecule显示类型也可以用engine: rdkit绘制。 chemistry可选依赖: 在服务器上安装rdkitPython 包。这个包负责按标准答案评分,chemistry导出也用它为未使用 RDKit.js 保存的答案补上 InChI 和 InChIKey。- 无需 WebGL: 两个工具包都以二维方式绘制。
- 离线: 结构来自条目本身或媒体目录中的文件,标准答案从不发送到浏览器。
不同安装方式下可用的功能如下:
| 功能 | 仅 OpenChemLib | 加 RDKit.js | 加 chemistry 可选依赖 |
|---|---|---|---|
| 编辑器、molfile、OpenChemLib SMILES 与 ID code、分子式、化合价检查 | 是 | 是 | 是 |
| 存储答案中的 RDKit SMILES、InChI 和 InChIKey | 否 | 是 | 导出时补上 |
| 绘制时的 RDKit 规整化检查 | 否 | 是 | 否 |
使用 engine: rdkit 的 molecule 显示类型 | 否 | 是 | 否 |
按 smiles、molfile 和 SMARTS 标准答案评分 | 否 | 否 | 是 |
按 inchikey 标准答案评分 | 否 | 是,使用浏览器计算的 InChIKey | 是 |
服务器端评分需要这个可选依赖,RDKit.js 则需要单独获取:
pip install 'potato-annotation[chemistry]'
potato vendor fetch rdkit # optional, InChIKeys in the browser获取完成后请重启服务器。缺少 RDKit.js 时,potato validate 会对每个保持 rdkit: auto 的 chemical_structure 方案发出警告,并给出获取命令。
配置
下面的配置用于 OCSR 校正。每个条目把原始插图和识别出的结构并排显示,标注者标记识别器识别错的原子,在编辑器中校正结构,并给出判定:
annotation_task_name: "Structure recognition check"
task_dir: .
media_directory: media
output_annotation_dir: annotation_output
data_files: [data/structures.json]
item_properties: {id_key: id, text_key: note}
instance_display:
layout:
direction: horizontal
fields:
- key: page_image # the figure cropped from the patent or paper
type: image
label: "Source image"
- key: ocsr_smiles # what the OCSR tool produced
type: molecule
label: "Recognized structure"
display_options:
height: 300
user_config:
allow_all_users: true
annotation_schemes:
- annotation_type: viewer_selection
name: wrong_atoms
description: "Mark the atoms the recognizer got wrong"
viewer_field: ocsr_smiles
labels: ["wrong element", "wrong bond or charge", "wrong stereo"]
- annotation_type: chemical_structure
name: corrected
description: "Correct the structure so it matches the source image"
prefill_field: ocsr_smiles
answer_field: true_smiles # graded in the export, never sent to the page
label_requirement: {required: true}
- annotation_type: radio
name: verdict
description: "How good was the recognition?"
labels: ["Correct", "Minor errors", "Wrong structure"]
label_requirement: {required: true}编辑器从 ocsr_smiles 开始。标注者原样保留的预填结构会成为保存的答案,并带有 changed: false,因此识别正确时不需要任何编辑。verdict 问题把已接受的结构与标注者从未检查过的条目区分开。Potato 会从页面收到的所有内容中删除 true_smiles 字段。在导出中,answer_correct 表示每个校正后的结构是否与 true_smiles 一致。把 answer_correct 与 changed 对照,可以区分漏掉的错误(未改动且错误)和引入的错误(改动过且错误)。
每个条目给出相对于 media_directory 的插图路径,并给出两个结构:
{"id": "fig1_aspirin", "note": "Compound 1 as printed.", "page_image": "fig1_aspirin.png",
"ocsr_smiles": "CC(=O)Oc1ccccc1C(=O)N", "true_smiles": "CC(=O)Oc1ccccc1C(=O)O"}根据名称绘制结构
名称到结构的练习只需要一个方案,以及每个条目的一个标准答案。下面的配置把化合物名称作为条目文本显示:
annotation_task_name: "Name to structure"
task_dir: .
output_annotation_dir: annotation_output
data_files: [data/names.json]
item_properties: {id_key: id, text_key: name}
user_config:
allow_all_users: true
annotation_schemes:
- annotation_type: chemical_structure
name: drawn
description: "Draw the structure this name describes"
answer_field: expected
rdkit: false # OpenChemLib only, no RDKit.js warning
editor_height: 420
label_requirement: {required: true}{"id": "q2", "name": "(S)-2-aminopropanoic acid", "expected": "C[C@H](N)C(=O)O"}学生可以用鼠标绘制,也可以键入 SMILES 字符串后按 Enter。设置 rdkit: false 后,页面只载入 OpenChemLib,不存储 InChIKey,由 chemistry 可选依赖在导出时为绘制结果评分。
在分子上标记原子
原子标记把 molecule 显示类型与 viewer_selection 方案配合使用。下面的配置请审编人员标记代谢位点,并用琥珀色绘制模型预测的位点作为参考:
annotation_task_name: "Sites of metabolism"
task_dir: .
output_annotation_dir: annotation_output
data_files: [data/molecules.json]
item_properties: {id_key: id, text_key: note}
instance_display:
fields:
- key: smiles
type: molecule
display_options:
show_atom_numbers: true
highlight_field: predicted_site # a model's predicted atoms, drawn in amber
user_config:
allow_all_users: true
annotation_schemes:
- annotation_type: viewer_selection
name: som
description: "Mark each atom where the compound is metabolized"
viewer_field: smiles
labels: ["hydroxylation", "N-dealkylation", "glucuronidation"]
min_selections: 1
label_requirement: {required: true}{"id": "m1", "note": "Predicted sites in amber.", "smiles": "CC(=O)Nc1ccc(O)cc1", "predicted_site": [0, 8]}highlight_field 指定的条目字段存放从 0 开始的原子索引,可以是列表,也可以是 "0, 8" 这样的文本。高亮是固定的,审编人员选择原子时不受高亮影响。min_selections 与 label_requirement 同时设置时,审编人员至少选中一个原子之前,Next 一直不可用。
结构数据
结构字段存放 SMILES 字符串、molfile 文本,或媒体目录中某个 .mol、.sdf、.smi 或 .smiles 文件的名称。服务器读取 .sdf 文件的第一条记录,以及 .smi 文件第一个非空行的第一个词元(token)。结构文本和结构文件的大小上限为 1 MB。不是 molfile 却含有空格的值会被拒绝,因为 SMILES 字符串中没有空格。显示类型从不获取 URL,Potato 也拒绝媒体目录之外的文件路径。
选项
chemical_structure 选项 | 默认值 | 含义 |
|---|---|---|
prefill_field | 无 | 存放 SMILES 字符串、molfile 或结构文件名的条目字段。编辑器从这个结构开始 |
prefill_format | auto | auto、smiles 或 molfile。需要设置 prefill_field |
answer_field | 无 | 存放标准答案的条目字段,在 chemistry 导出中评分。必须与 prefill_field 不同,并会从页面收到的内容中删除 |
rdkit | auto | auto 在已安装 RDKit.js 时使用它。true 要求 RDKit.js,缺少该构建时 potato validate 和服务器启动都会失败。false 只载入 OpenChemLib,并关闭警告 |
require_valid | true | 存在化合价问题或 RDKit.js 无法规整化的结构会使条目保持未完成。仅在方案为必填时生效 |
allow_empty | false | 接受空画布作为答案 |
editor_height | 380 | 编辑器的像素高度,取值 240 到 900 |
show_identifiers | true | 在编辑器下方显示 SMILES、分子式和 InChIKey |
instructions | 无 | 显示在问题下方的文本 |
label_requirement | 无 | {required: true} 使方案成为必填 |
molecule 显示选项 | 默认值 | 含义 |
|---|---|---|
engine | openchemlib | openchemlib 或 rdkit。使用 engine: rdkit 的配置在运行 potato vendor fetch rdkit 之前无法通过 potato validate |
height | 320 | 绘图高度,以像素或 CSS 长度表示 |
format | auto | auto、smiles 或 molfile |
show_atom_numbers | false | 在每个原子旁边标出它的 molfile 编号 |
atom_list | true | 在绘图下方显示原子按钮列表,这是用键盘进行选择的途径 |
highlight_field | 无 | 存放从 0 开始的原子索引的条目字段,这些原子以固定高亮绘制 |
highlight_color | #f59f00 | 固定高亮的十六进制颜色 |
max_atoms | 500 | 更大的分子不绘制,显示类型会给出说明。取值 1 到 5,000 |
allow_remote | false | 接受此选项,但它对这个显示类型没有作用 |
viewer_selection 选项 | 默认值 | 含义 |
|---|---|---|
viewer_field | 必填 | instance_display 中 molecule 字段的 key |
labels | 无 | 选择的名称,写成字符串或 {name, color}。新的选择使用当前标签,每个原子都可以更换标签 |
min_selections | 无 | 方案为必填时,选中的原子达到这个数量之前,条目保持未完成 |
max_selections | 无 | 超过这个数量的选择会被拒绝,并给出提示 |
instructions | 无 | 显示在问题下方的文本 |
标准答案
标准答案可以是一个单独的 SMILES 字符串、一个 molfile 或一个映射。映射中的所有部分都必须通过:
| 键 | 通过条件 |
|---|---|
smiles、molfile 或 inchikey | 所绘结构的 InChIKey 等于标准答案的 InChIKey。三者给出其一 |
ignore_stereo: true | 加在结构型键上时,只比较 InChIKey 的第一个 14 字符段,该段编码连接关系。不检查立体化学和同位素标记 |
substructure | 所绘结构包含该 SMARTS 模式,或包含列表中的每个模式 |
not_substructure | 所绘结构不包含其中任何一个 SMARTS 模式 |
精确模式比较标准 InChIKey。标准 InChI 把某些互变异构体视为同一结构,因此 2-吡啶酮(2-pyridone)的绘制结果可以通过以 2-羟基吡啶(2-hydroxypyridine)为标准答案的评分。电荷会被计入,因此即使设置了 ignore_stereo,乙氧基负离子(ethoxide)也无法通过以乙醇为标准答案的评分。子结构型标准答案为答案的某个特征评分,例如用 {"substructure": "C(=O)[OH]"} 判断是否含有羧酸。
answer_field 的值可以是单独的 SMILES 字符串。金标准条目和培训条目在方案名称下写映射形式:
{"id": "gold_1", "gold_label": {"corrected": {"smiles": "CC(=O)Oc1ccccc1C(=O)O"}}}
{"id": "train_1", "correct_answers": {"drawn": {"substructure": "C(=O)[OH]"}}}质量控制和培训阶段用与导出相同的比较方法为这些标准答案评分。编辑器只在标注阶段读取 prefill_field,因此培训条目打开时画布为空。
没有 rdkit Python 包时,smiles、molfile 或 SMARTS 标准答案会把每个答案都判为错误,服务器会记录一次缺少该包的日志。有一种情况仍然可用。inchikey 标准答案可以匹配由 RDKit.js 在标注者浏览器中计算出 InChIKey 的答案。安装了该包后,服务器会从存储的 molfile 重新计算 InChIKey,并忽略浏览器给出的值。
绘制与校正结构
要添加键,标注者先在左侧工具栏中选择键类型、环、元素或电荷,然后点击画布的空白处,或从某个原子拖出。把指针指向一个原子并键入元素符号(o、n、s)会改变该原子的元素。文本框接受 SMILES 字符串或 molfile,点击 Load(对 SMILES 字符串也可以按 Enter)会替换当前绘制的结构。OpenChemLib 无法读取的文本会被拒绝,当前结构保持不变。Clear 清空画布,Restore the given structure 恢复预填的结构。
编辑器下方的 SMILES、分子式和 InChIKey 在标注者绘制的过程中随之更新。化合价问题以“C (atom 2) has 5 bonds; it allows 4.”的形式列在那里;设置 require_valid 时,这些问题会阻止提交必填条目。
选择原子
标注者点击绘图中的原子来选中它,再次点击则取消选择。在原子列表中,Tab 移到列表,方向键、Home 和 End 在原子之间移动,Space 或 Enter 切换某个原子的选中状态。设置了 labels 时,标注者先选一个标签,列表中的每个原子都有一个标签菜单和一个 Remove 按钮。页面用元素和 molfile 编号命名原子,O4 就是源结构中的第四个原子。
存储的原子索引是该原子在所写源结构中从 0 开始的位置,按 SMILES 字符串中原子的顺序或 molfile 原子块中的顺序计算。molfile 原子编号等于索引加 1。无论由哪个引擎绘制分子,这个索引都等于 RDKit 以 removeHs=False 读取同一输入时的原子索引。OpenChemLib 会把 SMILES 字符串中的显式氢([H]C(Cl)(Br)F)并入相邻原子,因此在 engine: openchemlib 下,这样的 SMILES 字符串无法用于选择。显示器会给出提示,并且不提供任何可选原子。需要单独选择氢原子时,请使用 molfile 或 engine: rdkit。隐式氢(CCO)始终不可选择。
存储的答案
chemical_structure 方案为每个条目存储一个 JSON 对象:
{"molfile": "\nOCL MolfileCreator 2D\n\n 0 0 0 ... V3000\n...M END\n",
"smiles": "CC(Oc1ccccc1C(O)=O)=O",
"idcode": "dklB@@QmR[fUxUZBBF@@",
"formula": "C9H8O4",
"rdkit_smiles": "CC(=O)Oc1ccccc1C(=O)O",
"inchi": "InChI=1S/C9H8O4/c1-6(10)13-8-5-3-2-4-7(8)9(11)12/h2-5H,1H3,(H,11,12)",
"inchikey": "BSYNRYMUTXBXSQ-UHFFFAOYSA-N",
"changed": true,
"problems": [],
"toolkits": {"openchemlib": "9.25.0", "rdkit": "2026.03.6"}}molfile 为 V3000 格式。smiles 是 OpenChemLib 的规范异构 SMILES(isomeric SMILES),idcode 是 OpenChemLib 的规范 ID code。rdkit_smiles、inchi 和 inchikey 只在 RDKit.js 运行过时才出现。不同工具包生成的规范 SMILES 并不相同,因此应当用 InChIKey 或 ID code 比较结构,不要拿 smiles 和 rdkit_smiles 相比。只要所绘结构的 ID code 与预填结构相同,changed 就为 false。
molecule 显示类型上的 viewer_selection 方案存储选中的原子:
{"viewer": "molecule", "items": [{"atom": 12, "element": "N", "label": "wrong element"}]}导出
chemistry 导出覆盖 chemical_structure 方案,以及绑定到 molecule 显示类型的 viewer_selection 方案:
python -m potato.export --config config.yaml --format chemistry --output out/| 文件 | 内容 |
|---|---|
chemical_structures.csv | 每个答案一行,列为 user_id, instance_id, schema, smiles, rdkit_smiles, inchi, inchikey, idcode, formula, changed, problems, identifiers_from, answer_correct, answer_reason |
chemical_structures.sdf | 每个答案的 molfile,该行的非空字段作为 SD 属性 |
molecule_atoms.csv | 每个选中的原子一行,列为 user_id, instance_id, schema, viewer_field, atom, number, element, label,其中 number 是 molfile 编号 |
RDKit.js 计算了 InChI 和 InChIKey 时,identifiers_from 为 browser;导出用 rdkit Python 包计算了两者时为 server;两者都不可用时为 none。导出会为没有标识符的答案列出警告。对设置了 answer_field 的方案,answer_correct 为 1 或 0,answer_reason 给出评分理由,例如“a different structure (ignoring stereo)”或“missing the substructure C(=O)[OH]”。RDKit 的 SDMolSupplier 和其他 SD 读取器打开这个 SD 文件时,会把这些字段读作属性。
Potato 的一致性指标不覆盖 chemical_structure 或 viewer_selection 方案,第一个配置中的 verdict 单选题照常计算一致性。要比较标注者给出的结构,可以按 instance_id 和 schema 匹配 chemical_structures.csv 中的行,再比较 inchikey;没有任何 RDKit 可用时比较 idcode。对于原子选择,从 molecule_atoms.csv 逐条目计算集合重叠,例如基于 atom 索引的 Jaccard 系数。
示例
python potato/flask_server.py start examples/advanced/chemical-structure-correction/config.yaml -p 8000这个示例包含五张常见药物的结构图像,每张图像附一个识别出的 SMILES 字符串。其中四个识别结果经过手工修改,带有典型的识别错误,例如杂原子错误或立体中心反转。同一目录下的 generate_images.py 根据真实的 SMILES 绘制这些图像。
用编程智能体搭建研究
potato-skill 1.2.0 面向 Potato 2.13.1,认得 chemical_structure 和 molecule 这两个名称,并指导研究设计这一侧,例如向标注者问什么、每个结构由几名标注者检查、培训和注意力检查,以及导出。这个技能目前还没有记录结构数据格式、标准答案模式、显示选项和 chemistry 可选依赖。安装 potato-skill,把研究描述给你的智能体,再让智能体参考本页的 Markdown 副本 https://www.potatoannotator.com/docs/annotation-types/chemical-structures.md,以了解格式和选项。
延伸阅读
- 蛋白质结构 - 使用同一个
viewer_selection方案选择残基 - 实例显示 - 如何配置显示字段
- 质量控制 - 金标准条目和注意力检查
- 导出格式 - Potato 的导出格式和导出命令
- 生命科学 - 用 Potato 做生命科学与生物信息审编
有关实现细节,请参阅源文档。