Chemical Structure Annotation and OCSR Correction
Correct OCSR output, grade drawn structures against SMILES or SMARTS answer keys, and mark atoms on a molecule, then export InChIKeys and an SD file.
New in v2.13.0
The chemical_structure annotation scheme gives annotators a structure editor, and the molecule display draws a small molecule on which annotators select atoms. Chemistry dataset builders use the pair for structure correction. In structure correction, annotators compare a structure recognized from a figure in a patent or paper with the figure and fix the misread atoms and bonds. Chemistry education researchers and instructors use the editor to collect drawn structures, such as the structure a compound name describes, and Potato grades each drawing against an answer key on the server. Curators use the molecule display with a viewer_selection scheme to mark atoms, such as a reaction center or a site of metabolism. Each answer is stored as a molfile with the drawing's canonical SMILES, and the chemistry export writes every annotator's structures to a CSV file and an SD file.
Optical chemical structure recognition (OCSR) converts an image of a chemical structure into a machine-readable structure. Potato runs no OCSR model, and a study loads the recognized structures produced by another tool. SMILES writes a structure as a short line of text, such as CC(=O)O for acetic acid. An MDL molfile is the connection-table text format of the chemical table file family, and an SD file holds many molfiles with named data fields after each one. The InChIKey is a 27-character hash of the IUPAC InChI identifier, and two drawings of the same structure give the same InChIKey.
- Editor: OpenChemLib JS 9.25.0 (BSD-3-Clause), committed with Potato. The editor draws on a canvas with a toolbar for bonds, rings, elements, charges, stereo wedges, erasing, and undo. A text box under the canvas takes a typed or pasted SMILES string or molfile, and the text box is the keyboard route into the editor.
- RDKit.js (optional): the RDKit JavaScript MinimalLib 2026.3.6 (BSD-3-Clause) is a 7 MB WebAssembly build and is not committed.
potato vendor fetch rdkitdownloads the build and checks the download against the npm registry's integrity hash. With RDKit.js, the browser also computes the RDKit canonical SMILES, the InChI, and the InChIKey, flags structures RDKit cannot sanitize, and themoleculedisplay can draw withengine: rdkit. - The
chemistryextra (optional): installs therdkitPython package on the server. The package grades answer keys, and thechemistryexport uses the package to fill in InChI and InChIKey for answers saved without RDKit.js. - No WebGL: both toolkits draw in 2D.
- Offline: structures come from the item or from a file in the media directory, and answer keys never reach the browser.
The features available with each install are:
| Feature | OpenChemLib only | With RDKit.js | With the chemistry extra |
|---|---|---|---|
| Editor, molfile, OpenChemLib SMILES and ID code, formula, valence checks | yes | yes | yes |
| RDKit SMILES, InChI, and InChIKey in the stored answer | no | yes | filled in at export |
| RDKit sanitization check while drawing | no | yes | no |
molecule display with engine: rdkit | no | yes | no |
Grading against smiles, molfile, and SMARTS keys | no | no | yes |
Grading against an inchikey key | no | yes, with the InChIKey from the browser | yes |
Server-side grading needs the extra, and RDKit.js is a separate fetch:
pip install 'potato-annotation[chemistry]'
potato vendor fetch rdkit # optional, InChIKeys in the browserRestart the server after the fetch. While RDKit.js is missing, potato validate warns for each chemical_structure scheme left at rdkit: auto and names the fetch command.
Configuration
The config below sets up OCSR correction. Each item shows the source figure beside the recognized structure, and annotators mark the atoms the recognizer misread, correct the structure in the editor, and give a verdict:
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}The editor starts from ocsr_smiles. A pre-filled structure that the annotator leaves as given becomes the saved answer with changed: false, and a correct recognition therefore needs no edit. The verdict question separates an accepted structure from an item the annotator never checked. Potato removes the true_smiles field from everything the page receives. In the export, answer_correct says whether each corrected structure matches true_smiles. Comparing answer_correct with changed separates missed errors (unchanged and wrong) from introduced errors (changed and wrong).
Each item names the figure relative to media_directory and gives both structures:
{"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"}Drawing a structure from a name
A name-to-structure exercise needs one scheme and an answer key per item. The config below shows the compound name as the item text:
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"}Students can draw with the mouse or type a SMILES string and press Enter. With rdkit: false, the page loads OpenChemLib only and stores no InChIKey, and the chemistry extra grades the drawings at export.
Marking atoms on a molecule
Atom marking pairs a molecule display with a viewer_selection scheme. The config below asks curators to mark sites of metabolism and draws a model's predicted sites in amber for reference:
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]}The item field that highlight_field names holds 0-based atom indices, as a list or as text such as "0, 8". The highlight is fixed, and curators select atoms independently of the highlight. With min_selections and label_requirement together, Next stays blocked until the curator has selected at least one atom.
Structure data
A structure field holds a SMILES string, molfile text, or the name of a .mol, .sdf, .smi, or .smiles file in the media directory. The server reads the first record of an .sdf file and the first token of the first non-empty line of a .smi file. Structure text and structure files are limited to 1 MB. A value with spaces that is not a molfile is refused, because a SMILES string has no spaces. The display never fetches a URL, and Potato refuses a file path outside the media directory.
Options
chemical_structure option | Default | Meaning |
|---|---|---|
prefill_field | none | Item field with a SMILES string, a molfile, or a structure file name. The editor starts from the structure |
prefill_format | auto | auto, smiles, or molfile. Needs prefill_field |
answer_field | none | Item field with an answer key, graded in the chemistry export. Must differ from prefill_field, and is removed from what the page receives |
rdkit | auto | auto uses RDKit.js when installed. true requires RDKit.js, and potato validate and server start fail without the bundle. false loads OpenChemLib only and silences the warning |
require_valid | true | A structure with a valence problem, or one RDKit.js cannot sanitize, leaves the item incomplete. Enforced when the scheme is required |
allow_empty | false | Accepts an empty canvas as an answer |
editor_height | 380 | Editor height in pixels, from 240 to 900 |
show_identifiers | true | Shows the SMILES, formula, and InChIKey under the editor |
instructions | none | Text under the question |
label_requirement | none | {required: true} makes the scheme required |
molecule display option | Default | Meaning |
|---|---|---|
engine | openchemlib | openchemlib or rdkit. A config with engine: rdkit fails potato validate until potato vendor fetch rdkit has run |
height | 320 | Drawing height, in pixels or a CSS length |
format | auto | auto, smiles, or molfile |
show_atom_numbers | false | Draws each atom's molfile number beside the atom |
atom_list | true | Shows the list of atom buttons under the drawing, the keyboard route to selection |
highlight_field | none | Item field with 0-based atom indices drawn with a fixed highlight |
highlight_color | #f59f00 | Hex color of the fixed highlight |
max_atoms | 500 | Larger molecules are not drawn, and the display says so. From 1 to 5,000 |
allow_remote | false | Accepted, with no effect on this display |
viewer_selection option | Default | Meaning |
|---|---|---|
viewer_field | required | The key of the molecule field in instance_display |
labels | none | Names for selections, as strings or {name, color}. New selections take the current label, and each atom can be relabeled |
min_selections | none | The item stays incomplete until this many atoms are selected, when the scheme is required |
max_selections | none | Selecting more atoms is refused with a message |
instructions | none | Text under the question |
Answer keys
An answer key is a bare SMILES string, a molfile, or a mapping. All parts of a mapping must pass:
| Key | Passes when |
|---|---|
smiles, molfile, or inchikey | The drawn structure's InChIKey equals the key's InChIKey. Give one of the three |
ignore_stereo: true | Added to a structure key, compares only the first 14-character block of the InChIKey, which encodes connectivity. Stereo and isotope labels are not checked |
substructure | The drawn structure contains the SMARTS pattern, or every pattern in a list |
not_substructure | The drawn structure contains none of the SMARTS patterns |
The exact mode compares standard InChIKeys. Standard InChI treats some tautomers as one structure, and a drawing of 2-pyridone therefore passes against a key of 2-hydroxypyridine. Charges count, and ethoxide fails against ethanol even with ignore_stereo. A substructure key grades a feature of the answer, such as {"substructure": "C(=O)[OH]"} for a carboxylic acid.
An answer_field value may be a bare SMILES string. Gold-standard items and training items write the mapping form under the scheme's name:
{"id": "gold_1", "gold_label": {"corrected": {"smiles": "CC(=O)Oc1ccccc1C(=O)O"}}}
{"id": "train_1", "correct_answers": {"drawn": {"substructure": "C(=O)[OH]"}}}Quality control and the training phase grade these keys with the same comparison as the export. The editor reads prefill_field only in the annotation phase, and a training item therefore opens with an empty canvas.
Without the rdkit Python package, a smiles, molfile, or SMARTS key grades every answer wrong, and the server logs the missing package once. One case still works. An inchikey key matches an answer whose InChIKey RDKit.js computed in the annotator's browser. With the package installed, the server recomputes the InChIKey from the stored molfile and ignores the browser's value.
Drawing and correcting structures
To add a bond, the annotator clicks an empty part of the canvas or drags from an atom, after choosing a bond type, ring, element, or charge in the toolbar on the left. Pointing at an atom and typing an element symbol (o, n, s) changes the element. The text box takes a SMILES string or molfile, and Load (or Enter, for a SMILES string) replaces the drawing. Text OpenChemLib cannot read is refused, and the drawing is kept. Clear empties the canvas, and Restore the given structure brings back the pre-filled structure.
The SMILES, formula, and InChIKey under the editor update while the annotator draws. Valence problems are listed there in the form "C (atom 2) has 5 bonds; it allows 4.", and with require_valid the problems keep a required item from being submitted.
Selecting atoms
Annotators click an atom in the drawing to select the atom, and click again to remove the selection. In the atom list, Tab moves to the list, the arrow keys, Home, and End move between atoms, and Space or Enter toggles an atom. With labels, the annotator picks a label first, and each listed atom has a label menu and a Remove button. The page names atoms by element and molfile number, and O4 is the fourth atom of the source.
A stored atom index is the 0-based position of the atom in the source as written, in the order of atoms in the SMILES string or the molfile's atom block. The molfile atom number is the index plus 1. The index equals RDKit's atom index for the same input read with removeHs=False, whichever engine draws the molecule. OpenChemLib folds explicit hydrogens in a SMILES string ([H]C(Cl)(Br)F) into the neighboring atom, and with engine: openchemlib such a SMILES string cannot be used for selection. The display says so and offers no atoms at all. Use a molfile or engine: rdkit when individual hydrogens must be selectable. Implicit hydrogens (CCO) are never selectable.
Stored answer
A chemical_structure scheme stores one JSON object per item:
{"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"}}The molfile is V3000. smiles is OpenChemLib's canonical isomeric SMILES, and idcode is OpenChemLib's canonical ID code. rdkit_smiles, inchi, and inchikey are present only when RDKit.js ran. Canonical SMILES differ between toolkits, and structures should therefore be compared by InChIKey or ID code, never by smiles against rdkit_smiles. changed is false while the drawing has the pre-filled structure's ID code.
A viewer_selection scheme on a molecule display stores the selected atoms:
{"viewer": "molecule", "items": [{"atom": 12, "element": "N", "label": "wrong element"}]}Export
The chemistry export covers chemical_structure schemes and viewer_selection schemes bound to a molecule display:
python -m potato.export --config config.yaml --format chemistry --output out/| File | Contents |
|---|---|
chemical_structures.csv | One row per answer, with user_id, instance_id, schema, smiles, rdkit_smiles, inchi, inchikey, idcode, formula, changed, problems, identifiers_from, answer_correct, answer_reason |
chemical_structures.sdf | Each answer's molfile, with the row's non-empty fields as SD properties |
molecule_atoms.csv | One row per selected atom, with user_id, instance_id, schema, viewer_field, atom, number, element, label, where number is the molfile number |
identifiers_from is browser when RDKit.js computed the InChI and InChIKey, server when the export computed both with the rdkit Python package, and none when neither was available. The export lists a warning for answers left without identifiers. answer_correct is 1 or 0 for a scheme with an answer_field, and answer_reason gives the grader's reason, such as "a different structure (ignoring stereo)" or "missing the substructure C(=O)[OH]". RDKit's SDMolSupplier and other SD readers open the SD file with the fields as properties.
Potato's agreement metrics do not cover chemical_structure or viewer_selection schemes, and the verdict radio question in the first config gets agreement as usual. To compare annotators' structures, match rows of chemical_structures.csv on instance_id and schema and compare inchikey, or idcode when no RDKit was available. For atom selections, compute set overlap per item from molecule_atoms.csv, for example Jaccard over the atom indices.
Example
python potato/flask_server.py start examples/advanced/chemical-structure-correction/config.yaml -p 8000The example has five structure images of common drugs and a recognized SMILES string for each image. Four of the recognized strings were edited by hand to contain a typical recognition error, such as a wrong heteroatom or an inverted stereocenter. generate_images.py in the same directory draws the images from the true SMILES.
Setting up a study with a coding agent
potato-skill 1.2.0 targets Potato 2.13.1 and knows chemical_structure and molecule by name, and the skill guides the study-design side, such as what to ask annotators, how many annotators check each structure, training and attention checks, and export. The skill does not yet document the structure data formats, the answer-key modes, the display options, or the chemistry extra. Install potato-skill, describe the study to your agent, and point the agent at the Markdown copy of this page, https://www.potatoannotator.com/docs/annotation-types/chemical-structures.md, for the formats and options.
Further Reading
- Protein Structures - Residue selection with the same
viewer_selectionscheme - Instance Display - How display fields are configured
- Quality Control - Gold-standard items and attention checks
- Export Formats - Potato's export formats and the export command
- Life Sciences - Potato for life sciences and biocuration
For implementation details, see the source documentation.