Skip to content

Digital Humanities in Potato: IIIF, Web Annotation, PAGE XML, TEI and Zooniverse

Import IIIF manifests, W3C Web Annotations, PAGE XML and TEI into Potato, correct HTR lines, run a Zooniverse workflow privately, and export back to each format.

Potato 2.12.0 reads and writes four interchange formats used in digital humanities: IIIF manifests, W3C Web Annotation, PAGE XML and TEI. Each importer builds a runnable project from a library manifest, an annotation collection, a Transkribus or eScriptorium export, or a TEI edition, and each exporter writes the corrected work back to the format it came from. A Zooniverse workflow and its classifications import the same way.

FormatImportExportTypical source
IIIF Presentation API 3 (and 2.x) manifests-f iiif-f iiif_annotationsLibrary and museum image collections, Mirador, Universal Viewer
W3C Web Annotation (JSON-LD)-f web_annotation-f web_annotationAnnotorious, Mirador, Hypothesis
PAGE XML-f page_xml-f page_xmlTranskribus, eScriptorium, OCR-D
TEI P5-f tei-f teiScholarly editions, corpora

The source's annotations come in as pre-annotations, which an annotator sees as a starting point and which are stored only once someone saves.

bash
potato import -i <file or folder> -o <project folder> [-f <format>]
python potato/flask_server.py start <project folder>/config.yaml -p 8000
python -m potato.export -c <project folder>/config.yaml -f <format> -o <output folder>

Local files are copied only from the input folder. An importer copies a local image or text into the project's media/ folder only when the file is inside the folder you import, or a folder you name with --image-dir or --texts. Any other reference, such as an absolute path, a file:// URI, a ../ path or a symlink that leads out of the folder, is skipped with a warning, because the server publishes media/. Move the file into the input folder and import again.

IIIF manifests

bash
potato import -f iiif -i manifest.json -o book-project
potato import -f iiif -i https://example.org/iiif/book1/manifest.json -o book-project

The input is a manifest file, a folder holding one, or an http(s) URL. A URL is the only input that makes the importer use the network: it fetches the manifest and any annotation pages the manifest references by URL. For a local manifest, referenced pages are read from files of the same name beside it.

Each Canvas becomes one item. The image is the painting annotation's body id, or, when the body has no usable id or --iiif-size is given, a request to the body's Image service at that size (default !2000,2000). The manifest and canvas labels and metadata are shown above the image. Annotation pages on the canvas become shapes on an image_annotation scheme named regions, with their labels taken from the annotations' tags. Canvas coordinates are converted, so a canvas whose size differs from the image's pixel size still lines up. Presentation 2.x manifests are read on a best-effort basis.

bash
python -m potato.export -c book-project/config.yaml -f iiif_annotations -o out \
    --option base_url=https://example.org/iiif-out/

The exporter writes one annotation page per annotated canvas to out/annotations/<canvas>.json, and a copy of the manifest to out/manifest.json with a reference to each page, in the manifest's own version. Open the copied manifest in Mirador or Universal Viewer to see the annotations. Boxes become #xywh= fragments, polygons, polylines, ellipses and freehand shapes become SVG selectors, landmarks become point selectors, and question answers become textual bodies on the canvas, each with the Potato user who drew it as creator. --option embed=true puts the annotation pages inside the manifest copy, so nothing else needs hosting. Masks have no IIIF form and are left out.

The example examples/image/iiif-manifest/ has two generated folios, one with annotations already on it.

W3C Web Annotation

bash
potato import -f web_annotation -i annotations.jsonld -o project --texts documents/

The importer accepts an AnnotationCollection, an AnnotationPage, a list of annotations or pages, a single Annotation, the Hypothesis API search response, and IIIF 2.x sc:AnnotationList files.

TargetBecomes
TextQuoteSelector or TextPositionSelectorA span on a span scheme
FragmentSelector xywh=, or a #xywh= fragment on the targetA box on an image_annotation scheme
SvgSelector with a polygon, polyline, rect, circle, ellipse or pathA polygon, polyline, box or ellipse
PointSelectorA landmark
The whole resourceA radio or multiselect answer from tagging and classifying bodies, or a text answer from describing and commenting bodies

A span needs the text it was drawn on, which the importer looks for in --texts: a folder of text files named after the last path segment of each target, or a JSONL or JSON file of rows with text and one of source, id, url, uri or iri. When a position and a quote disagree, the span is found again by its quote, with prefix and suffix choosing between repeated occurrences. Pixel regions need the image size, which comes from --texts rows, the image file, or an SVG selector's viewBox. Pixel regions on an image of unknown size are left out with a warning rather than placed at a guessed scale.

Each annotation's creator becomes that person's answer, and with --keep-annotators those answers are written as each person's saved work. A creator of type Software is recorded as the model that made the suggestion. The web_annotation export writes web_annotations.jsonld, one AnnotationCollection of every span, region and answer.

PAGE XML and HTR line correction

bash
potato import -f page_xml -i transkribus-export/ -o htr-project                 # lines (default)
potato import -f page_xml -i transkribus-export/ -o layout-project --page-xml-mode regions

The input is one .xml file or a folder, searched recursively, so a Transkribus export with its page/ subfolder works. Each page's image is looked for beside the XML, one folder up and in --image-dir. The 2013-07-15 and 2019-07-15 schema versions are read.

Lines mode, the default, makes one item per TextLine, in the page's reading order. The annotator sees the line cropped out of the page with the image_region display and corrects the recognised text in a text_edit scheme, which starts from the line's text and shows a word diff against it. When the recogniser stored a confidence, it comes across as htr_confidence. Regions mode makes one item per page, with every region and text line as a polygon on an image_annotation scheme named layout, labelled by region type.

image_region shows one rectangle of a larger image. The browser downloads the page image once and crops it, so moving between the lines of one page does not download it again:

yaml
instance_display:
  fields:
    - key: line_image
      type: image_region
      label: Line
      padding: 12
json
{"image": "/media/page-001.png", "width": 2480, "height": 3508,
 "region": [412, 980, 1630, 96],
 "polygon": [[412, 980], [2042, 984], [2040, 1076], [410, 1072]]}
OptionDefaultMeaning
padding8Image pixels shown around the region on each side
outlinetrueDraws the polygon or rectangle over the crop
show_pagetrueAdds a "Show the whole image" disclosure with the region outlined on the full page
max_scale2.0The crop is never drawn wider than this many times its size in pixels
alt_text""Accessible name of the crop
bash
python -m potato.export -c htr-project/config.yaml -f page_xml -o out

The exporter opens the project's copy of each file, changes only what was corrected, and writes everything else back as it was, including metadata, reading order and the schema version. A corrected line gets its new text, and the recogniser's confidence is removed. In layout mode, moved shapes update their element's coordinates, new shapes become new lines or regions, and deleted ones are removed with their reading-order reference. Lines nobody annotated are left unchanged.

The example examples/text/htr-line-correction/ has a generated page image with five lines and a PAGE XML file whose recognised text has errors to correct.

TEI P5

bash
potato import -f tei -i edition/ -o tei-project              # one item per block
potato import -f tei -i letters.xml -o tei-project --tei-unit div

--tei-unit sets what counts as one item: p (paragraphs, heads and line groups, the default), div, s or l. Inline <persName>, <placeName>, <orgName>, <geogName>, <name>, <date> and <time> become spans labelled with the element name, and <rs type="ship"> is labelled ship. The item text is a reading text: <choice> takes the regularised, corrected or expanded form, <del>, <note> and <fw> are left out, <lb/> is a newline, and <gap/> is [...].

The tei export writes one TEI document per source file, wrapping each span in its label when the label is a TEI name element and in <rs type="LABEL"> otherwise. A span that crosses another cannot nest in XML, so it goes into <standOff><listAnnotation> as a string-range annotation, and the export summary reports how many did. The exporter builds a new document from the project and does not edit the source file, so header metadata other than the title is not carried over.

Zooniverse

potato import reads a Zooniverse project's exports: the workflow's tasks become annotation schemes, the subjects become items, and the volunteers' classifications can come across as their saved work. It suits running a workflow privately, with unpublished data, a lab team or paid annotators, or computing agreement on an existing project's classifications.

From the project's Lab → Data Exports, request the workflow, classification and subject exports, and put the three CSV files in one folder:

bash
potato import -i zooniverse-exports/ -o my-project --keep-annotators
Zooniverse taskPotato
Question (single answer)radio
Question (multiple answers), shortcutmultiselect
Texttext
Sliderslider
Dropdown (simple)select
Drawing: point, rectangle, polygon, line, circle or ellipse, freehandimage_annotation with the matching tool
Highlighterspan, when the subject's text is in its metadata
ComboIts tasks, each as above
Branchingdisplay_logic on the tasks reached through that answer

Zooniverse shows tasks one after another, and Potato shows them on one page, revealing each one when the answers that lead to it are given. One workflow version is imported, by default the most recent major version the classifications use. Survey tasks (species identification with follow-up questions), drawing sub-task questions, transcription tasks and classifications covering several subjects are counted in the output and not imported. Volunteers who were not logged in appear as anon_<id>, so one person may show up as several.

Further Reading

For implementation details, see the source documentation on digital humanities formats and Zooniverse.