化學結構標註與 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 做生命科學與生物資訊審編
有關實現細節,請參閱源文件。