Skip to content

化學結構標註與 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 可選依賴: 在伺服器上安裝 rdkit Python 包。這個包負責按標準答案評分,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 則需要單獨獲取:

bash
pip install 'potato-annotation[chemistry]'
potato vendor fetch rdkit     # optional, InChIKeys in the browser

獲取完成後請重啟伺服器。缺少 RDKit.js 時,potato validate 會對每個保持 rdkit: auto 的 chemical_structure 方案發出警告,並給出獲取命令。

配置

下面的配置用於 OCSR 校正。每個條目把原始插圖和識別出的結構並排顯示,標註者標記識別器識別錯的原子,在編輯器中校正結構,並給出判定:

yaml
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 的插圖路徑,並給出兩個結構:

json
{"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"}

根據名稱繪製結構

名稱到結構的練習只需要一個方案,以及每個條目的一個標準答案。下面的配置把化合物名稱作為條目文本顯示:

yaml
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}
json
{"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 方案配合使用。下面的配置請審編人員標記代謝位點,並用琥珀色繪製模型預測的位點作為參考:

yaml
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}
json
{"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_formatautoauto、smiles 或 molfile。需要設定 prefill_field
answer_field無存放標準答案的條目欄位,在 chemistry 匯出中評分。必須與 prefill_field 不同,並會從頁面收到的內容中刪除
rdkitautoauto 在已安裝 RDKit.js 時使用它。true 要求 RDKit.js,缺少該構建時 potato validate 和伺服器啟動都會失敗。false 只載入 OpenChemLib,並關閉警告
require_validtrue存在化合價問題或 RDKit.js 無法規整化的結構會使條目保持未完成。僅在方案為必填時生效
allow_emptyfalse接受空畫布作為答案
editor_height380編輯器的畫素高度,取值 240 到 900
show_identifierstrue在編輯器下方顯示 SMILES、分子式和 InChIKey
instructions無顯示在問題下方的文本
label_requirement無{required: true} 使方案成為必填
molecule 顯示選項預設值含義
engineopenchemlibopenchemlib 或 rdkit。使用 engine: rdkit 的配置在執行 potato vendor fetch rdkit 之前無法通過 potato validate
height320繪圖高度,以畫素或 CSS 長度表示
formatautoauto、smiles 或 molfile
show_atom_numbersfalse在每個原子旁邊標出它的 molfile 編號
atom_listtrue在繪圖下方顯示原子按鈕列表,這是用鍵盤進行選擇的途徑
highlight_field無存放從 0 開始的原子索引的條目欄位,這些原子以固定高亮繪製
highlight_color#f59f00固定高亮的十六進位制顏色
max_atoms500更大的分子不繪製,顯示類型會給出說明。取值 1 到 5,000
allow_remotefalse接受此選項,但它對這個顯示類型沒有作用
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 字串。金標準條目和培訓條目在方案名稱下寫對映形式:

json
{"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 物件:

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 方案儲存選中的原子:

json
{"viewer": "molecule", "items": [{"atom": 12, "element": "N", "label": "wrong element"}]}

匯出

chemistry 匯出覆蓋 chemical_structure 方案,以及繫結到 molecule 顯示類型的 viewer_selection 方案:

bash
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 係數。

示例

bash
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,以瞭解格式和選項。

延伸閱讀

有關實現細節,請參閱源文件。