AI-assisted handwritten text recognition for Omeka S: architecture and module

Transcription Manager is an Omeka S module that sends page images to Claude’s vision API, gives you a review screen to correct the results, and publishes the finished text onto the item as a media attachment. It installs like any Omeka S module and ships the AWS backend it requires: two Lambda functions and a SAM template you deploy to your own account.

The problem

Conventional OCR returns nothing usable on handwritten nineteenth-century material. At Marshall District Library this runs against minute books, ledgers, correspondence, and pages ruled into columns: assessment rolls, tax lists, registers.

Architecture

A page against a vision model takes tens of seconds, and a book is many pages. No PHP request stays open that long, and job state must survive the browser closing.

So the module works around this by handling one page at a time. The admin fires page one at API Gateway with a two-second cURL timeout and returns immediately. Lambda transcribes the page, POSTs back to a webhook route in the module, and that callback stores the result and dispatches the next page. This allows for large numbers of files to be transcribed so a user can close the browser and come back later to a finished job.

Both directions are HMAC-SHA256 over the raw body, checked with hmac.compare_digest in the Lambda and hash_equals in PHP. The webhook route is anonymous, because AWS calls it unauthenticated, and refuses every request when the secret is unset.

Publication is deliberately a second step: a finished job is a draft, and someone reads the text beside the page image, edits it, then publishes, with edits winning over the model’s output. A ruled page publishes its table as data, not flattened prose.

A second pass locates each person name and returns normalized bounding boxes, which become regions you can correct, add to, or delete by hand. Rows key on the immutable IIIF image id, not a positional canvas URI that renumbers when media are reordered, and each records the canvas dimensions it was computed against, so a region survives the page being re-derived at a different size. Two public read endpoints serve a decoupled frontend with published or approved rows only.

What you need to run it

Omeka S 4.0+, PHP 8.1+, an AWS account with the SAM CLI, and an Anthropic API key. Install the module from the admin, run sam deploy, then paste four values into the config screen. Your Omeka S install must be reachable from AWS, since the Lambda calls back to it.

Known limitations

  • No PHP test suite, and stranded pages are not retried.
  • Webhooks carry no timestamp or nonce, so a captured callback can be replayed.
  • Cost per page is typed in, not read back from Anthropic.
  • The prompts were tuned against English-language handwriting and ship as written, since a redacted prompt teaches nothing; they encode opinions about layout that may not suit your material.

Links

Would love to hear back if anyone has found a cleaner way to run longer jobs in Omeka S.

Hi,

In your article, you write that:

Omeka S is a PHP application with no built-in support for background workers or long-running processes.

Despite the quality of your work and the justification of your technical choice, I disagree with this statement, as Omeka S offers a way to handle background jobs using the PHP-CLI to perform time-consuming tasks asynchronously. The bulk image IIIF-tiling process is a good example of this feature.

1 Like

Thanks for the feedback! I will look into that and make some adjustments. How does using the php-cli impact performance on the server itself? We have some items that have 100s of images that need to be processed that are all large files. Would the php-cli approach potentially cause server performance issues when we are running large transcription jobs?