Transkribus와 eScriptorium의 HTR 출력을 줄 단위로 교정하기
Potato에서 두 전사자가 페이지 이미지와 대조해 각 HTR 줄을 확인하고, 재학습을 위해 PAGE XML을 내보내고, 모델의 문자 오류율을 계산합니다.
Transkribus나 eScriptorium에서 얻은 필기 텍스트 인식(HTR) 출력이 있는 기록물 관리자, 역사학자, 편집자는 이 튜토리얼을 따라 Potato 2.13.1에서 두 전사자가 인식된 모든 줄을 교정하게 하고, 교정을 재학습용 PAGE XML에 다시 쓰고, 교정으로부터 모델의 문자 오류율을 계산할 수 있습니다. PAGE XML은 Transkribus, eScriptorium, Kraken 엔진이 페이지 이미지 한 장의 영역, 텍스트 줄, 인식된 텍스트를 저장하는 XML 형식입니다. Potato는 텍스트 줄마다 항목 하나를 만들고, 페이지 이미지에서 그 줄을 잘라 내고, 인식된 텍스트를 잘라 낸 이미지 아래의 편집 가능한 상자에 넣습니다. 실제 사례는 17세기 의회 회의록 네 페이지를 쓰며, 모든 줄에 정답 전사가 있습니다. 따라서 이 페이지의 오류율은 정답 키와 대조해 확인할 수 있습니다. 디지털 인문학 형식은 모든 가져오기 및 내보내기 옵션을 설명하며, 이 페이지는 PAGE 파일에서 교정된 파일까지 교정 프로젝트 하나를 따라갑니다.
페이지와 인식된 텍스트
예시 페이지는 1470년부터 1805년까지 볼차노(Bozen) 시의회 회의록인 Ratsprotokolle에서 가져왔습니다. 회의록은 초기 근대 독일어로 손으로 쓰였습니다. READ 프로젝트는 페이지 450장과 각 줄의 정답 전사를 PAGE XML로 HTR Dataset ICFHR 2016이라는 이름으로 CC BY 4.0 아래 공개했습니다. 테스트 세트는 Test-ICFHR-2016.tgz(59MB)이며, 페이지 이미지 50장과 이미지당 하나씩 PAGE 파일이 든 page/ 폴더가 있습니다. Potato의 가져오기는 Transkribus 내보내기에서 그 폴더 구조를 기대합니다. 이 튜토리얼은 405쪽부터 408쪽을 사용합니다. 네 페이지에는 텍스트 줄 88개와 정답 1,732자가 있습니다.
데이터셋의 PAGE 파일에는 모델의 판독이 아니라 정답이 들어 있습니다. 그래서 Potato 팀은 교정할 텍스트를 만들기 위해 데이터셋 자체의 줄 경계 위에서 HTR 모델을 실행했으며 Transkribus는 쓰지 않았습니다. 모델은 Kraken용 Stefan Weil의 독일어 필기체 모델이며, Weil은 이 모델을 CC BY-SA 4.0으로 공개했습니다. Kraken은 eScriptorium 안의 인식 엔진입니다. Kraken은 XML 파일 옆에서 페이지 이미지를 찾으므로, 먼저 네 개의 PAGE 파일을 page/에서 복사하세요.
pip install kraken==7.1.1
cd Test-ICFHR-2016
mkdir -p ../htr/page
for n in 0405 0406 0407 0408; do
cp page/Seite$n.xml .
kraken -f page -x -i Seite$n.xml ../htr/page/Seite$n.xml ocr -m ../german_handwriting.mlmodel
cp Seite$n.JPG ../htr/
done-f page는 줄을 다시 찾는 대신 PAGE 파일에서 줄의 다각형을 읽고, -x는 PAGE XML을 씁니다. 그러면 htr/ 폴더는 이미지가 맨 위에 있고 PAGE 파일이 htr/page/에 있는 Transkribus 구조가 됩니다. Kraken은 각 줄의 신뢰도를 썼고, 88개 줄 중 52개에서 신뢰도가 0.95 이상이었습니다. 정답과 비교하면 Kraken의 텍스트는 테스트 페이지 50장 전체에서 문자 오류율이 8.4%였습니다. 모델의 학습 데이터에는 데이터셋의 학습 페이지 400장이 포함되며, 테스트 페이지 50장은 학습 페이지에 들어 있지 않습니다. 따라서 Kraken은 이 컬렉션의 필체를 본 적이 있으며, 모델에 새로운 컬렉션에서는 오류율이 더 높게 나올 수 있습니다. 직접 만든 Transkribus나 eScriptorium 내보내기가 있다면 htr/ 대신 그 내보내기를 두고 다음 단계부터 시작하세요.
PAGE XML을 Potato로 가져오기
Potato를 설치하고 폴더를 가져옵니다.
pip install potato-annotation==2.13.1
potato import -f page_xml -i htr/ -o htr-correction/Imported 88 item(s), 1 question(s), 0 answer(s) from 0 annotator(s), and 0 model suggestion(s) from PAGE XML.
wrote htr-correction/data/htr.jsonl
wrote htr-correction/page_xml/page/Seite0405.xml
wrote htr-correction/media/Seite0405.JPG
...
wrote htr-correction/config.yaml
wrote htr-correction/README.md-f transkribus는 같은 가져오기를 가리킵니다. 가져오기는 폴더에서 PAGE 파일을 찾고, 각 페이지의 이미지를 XML 옆과 한 단계 위 폴더에서 찾아 이미지를 media/로 복사합니다. 다른 곳에 있는 이미지는 폴더 경로와 함께 --image-dir를 추가하세요. PAGE 파일의 imageFilename이 IIIF 이미지 URL 같은 웹 주소이면, 항목은 그 주소를 유지하고 브라우저가 해당 서버에서 이미지를 불러옵니다. 가져오기는 각 PAGE 파일의 사본도 page_xml/ 아래에 보관하며, 내보내기는 나중에 그 사본에 교정을 씁니다.
각 줄은 페이지의 읽기 순서대로 data/htr.jsonl의 항목 하나가 되었습니다. 405쪽의 두 번째 줄이 필드를 보여 줍니다.
{"id": "Seite0405-line_b0d3dab2-88ff-4574-9081-768f21788681",
"htr_text": "schofft. Aūch das Er der",
"line_image": {"image": "/media/Seite0405.JPG", "width": 2322, "height": 3511,
"region": [700.0, 500.0, 1055.0, 97.0],
"polygon": [[1755.0, 507.0], [1405.0, 500.0], [700.0, 508.0], [700.0, 597.0], [1405.0, 589.0], [1755.0, 596.0]],
"baseline": [[700.0, 568.0], [1405.0, 560.0], [1755.0, 567.0]]},
"page": "Seite0405.xml", "page_xml": "page_xml/page/Seite0405.xml",
"region_id": "region_2a649214-6882-4084-978c-59e74c45047f",
"line_id": "line_b0d3dab2-88ff-4574-9081-768f21788681",
"line_number": 2, "htr_confidence": "0.9855"}htr_text는 Kraken의 판독이고, line_image는 페이지에서 그 줄의 위치를 나타내며, htr_confidence는 그 줄에 대한 Kraken의 신뢰도입니다. line_id는 항목을 PAGE 파일의 TextLine 요소와 연결합니다. 영역과 줄 자체를 교정하려면, --page-xml-mode regions가 각 페이지를 모든 영역과 줄이 다각형인 항목 하나로 가져옵니다.
교정 설정
가져오기는 text_edit 스킴 하나와 image_region 디스플레이를 가진 작동하는 설정을 씁니다. 아래 설정은 가져오기가 쓴 설정에 다섯 가지를 바꿉니다.
- 작업 이름이
htr (imported)를 대체합니다. - **
num_annotators_per_item: 2**는 모든 줄을 두 전사자에게 보냅니다. - **
assignment_strategy: fixed_order**는 줄을 읽기 순서대로 제공하므로, 전사자는 한 페이지의 줄을 필경사가 쓴 순서대로 읽습니다. - 두 번째 디스플레이 필드는 그 줄에 대한 Kraken의 신뢰도를 보여 줍니다.
- 스킴의 설명은 전사 규약을 밝히는데, 서로 다른 규약을 따르는 두 전사자는 같은 줄에 대해 다른 텍스트를 만들기 때문입니다.
설정은 완전하며 2.12.1과 2.13.1에서 python -m potato.validate_cli config.yaml --strict를 통과합니다.
port: 8000
annotation_task_name: Ratsprotokolle line correction
task_dir: .
output_annotation_dir: annotation_output/
data_files:
- data/htr.jsonl
item_properties:
id_key: id
text_key: htr_text
user_config:
allow_all_users: true
users: []
site_dir: default
assignment_strategy: fixed_order
num_annotators_per_item: 2
instance_display:
fields:
- key: line_image
type: image_region
label: Line
- key: htr_confidence
type: text
label: Kraken confidence
annotation_schemes:
- annotation_type: text_edit
name: transcription
description: Correct the transcription of this line. Keep the scribe's spelling and abbreviations.
source_field: htr_text
show_diff: true
show_edit_distance: true
allow_reset: trueshow_diff는 Kraken의 텍스트와의 단어 단위 diff를 보여 주고, show_edit_distance는 바뀐 단어와 글자의 개수를 보여 주며, allow_reset은 Kraken의 텍스트를 복원하는 버튼을 추가합니다. 실제 프로젝트의 설명에는 다음 절의 ū와 ¬처럼 각 특수 문자를 명시해야 합니다.
줄 교정하기
프로젝트 폴더에서 서버를 시작하고, 각 전사자가 사용자 이름과 비밀번호로 등록하게 합니다.
cd htr-correction
potato start config.yaml -p 8000페이지는 페이지 이미지에서 잘라 낸 줄 위에 줄의 윤곽선이 그려진 모습을 보여 주고, "Show the whole image"는 해당 줄이 표시된 전체 페이지를 엽니다. 잘라 낸 이미지와 Kraken의 신뢰도 아래에서 "Original"은 Kraken의 텍스트를 보여 주고, "Edit below" 아래의 상자는 같은 텍스트로 시작합니다. 전사자가 입력하는 동안 "Words changed"와 "Chars changed"가 수정을 세고, "Changes"는 바뀐 단어마다 취소선을 긋고 옛 단어 옆에 새 단어를 보여 줍니다. diff는 전체 단어 단위로 작동합니다. Kraken은 405쪽의 첫 줄을 203 대신 263으로 읽었으며, diff는 숫자 전체에 취소선을 긋고 새 숫자를 추가하지만, 글자 수는 하나가 바뀌었다고 셉니다.
이미 맞는 줄에서는 다음을 클릭하세요. Potato 2.13.1은 바꾸지 않은 줄을 편집 거리 0의 수락된 줄로 저장하므로, 내보내기는 그 줄을 아무도 열지 않은 줄과 구분합니다. Potato 2.12.1은 상자의 텍스트가 바뀌거나 재설정 버튼이 실행된 뒤에만 text_edit 답변을 저장했습니다. 2.12.1에서는 손대지 않은 줄이 답변 없이 저장되었고 진행 카운터도 그 줄을 세지 않았으므로, 그 버전에서는 다음을 누르기 전에 "Reset to Original"을 클릭하세요. 2.12.1에서 이 페이지를 위해 브라우저로 확인했을 때, 다음만 눌러 지나간 줄은 카운터를 88개 중 1에 그대로 두었고, 다음 줄의 "Reset to Original"이 카운터를 2로 옮겼습니다.
READ 전사는 스킴의 설명에 넣을 두 가지 규약을 따릅니다. 첫째는 필경사가 u와 n을 구분하려고 u 위에 쓴 갈고리를 나타내는 ū이고, 둘째는 단어가 줄바꿈으로 나뉘는 줄 끝의 ¬입니다. 네 페이지의 정답에는 ū가 37개, ¬가 18개 있습니다.
이 페이지의 바탕이 된 테스트 실행
이 페이지의 수치는 정답을 정답 키로 사용한 스크립트 전사자 두 명과 함께 Potato 2.12.1에서 실행한 결과에서 나왔습니다. 스크립트는 어노테이션 페이지가 보내는 것과 같은 요청으로 각 줄을 저장했습니다. 첫 번째 전사자 alice는 각 줄의 정답을 그대로 입력했습니다. 두 번째 전사자 bob은 모든 ū를 일반 u로 바꿔 정답을 입력했습니다. Bob의 규칙은 정답과 다른 규약을 따르는 전사자를 나타냅니다. 정답이 Kraken의 텍스트와 일치하는 줄에서는 각 전사자가 "Reset to Original"이 줄을 저장하는 방식으로 줄을 저장했습니다. Alice는 88개 줄 중 56개를, Bob은 69개를 바꿨는데, Bob은 Kraken이 올바르게 읽은 줄에서도 갈고리를 제거했기 때문입니다. 실제 전사자는 줄을 잘못 읽기도 하지만, 스크립트는 잘못 읽는 일이 없습니다.
교정된 PAGE XML 내보내기
page_xml 내보내기는 각 전사자의 교정을 프로젝트의 PAGE 파일 사본에 씁니다.
python -m potato.export -c config.yaml -f page_xml -o export/export/alice/page_xml/page/Seite0405.xml ... Seite0408.xml
export/bob/page_xml/page/Seite0405.xml ... Seite0408.xml전사자가 두 명이면 전사자마다 폴더가 생기며, 내보내기는 176개 줄을 썼고 125개 줄이 바뀌었다고 보고했습니다. --option annotator=alice는 사용자 폴더 없이 한 전사자의 버전을 씁니다. 저장된 각 줄에서 내보내기는 줄의 TextEquiv 텍스트를 설정하고 그 줄에 대한 Kraken의 신뢰도를 제거합니다. 텍스트가 바뀐 줄에서는 Kraken의 Word 요소와 그 안의 Glyph 요소도 제거합니다. 옛 단어와 글리프의 위치가 새 텍스트와 더 이상 맞지 않기 때문이며, --option keep_words=true는 그래도 요소를 유지합니다. 내보내기는 LastChange 날짜를 갱신하고 각 파일의 나머지 내용은 Kraken이 쓴 그대로 둡니다. Alice의 네 파일은 정답과 글자 차이 없이 일치했습니다.
교정된 파일은 다음 모델의 학습 데이터입니다. eScriptorium은 PAGE XML 전사를 가져오고 문서의 전사로 모델을 학습합니다. Kraken 자체 도구는 페이지 이미지가 옆에 있으면 파일을 읽으며, ketos test는 교정된 텍스트에 대한 모델의 오류율을 보고합니다.
cp media/*.JPG export/alice/page_xml/page/
ketos test -f page -m german_handwriting.mlmodel export/alice/page_xml/page/*.xml=== report ===
1732 Characters
151 Errors
91.28% Character Accuracy문자 정확도 91.28%는 문자 오류율 8.72%입니다. 같은 파일로 모델을 미세 조정합니다.
ketos train -f page -i german_handwriting.mlmodel --resize union -o finetuned export/alice/page_xml/page/*.xml-i는 기존 모델에서 시작하고, --resize union은 교정에 쓰였지만 모델에 없는 문자를 추가하며, -o는 새 모델의 폴더 이름을 지정합니다. 네 페이지에 대해 한 에포크 실행하자 88개 줄이 학습용 79개와 검증용 9개로 나뉘었고 모델 파일이 폴더에 쓰였습니다. 네 페이지는 더 나은 모델을 만들기에 충분한 데이터가 아니라 경로를 시험하는 정도이므로, 재학습하기 전에 훨씬 더 많은 페이지에서 교정을 모으세요.
교정 전후의 문자 오류율
문자 오류율(CER)은 모델의 텍스트를 교정된 텍스트로 바꾸는 데 필요한 문자 삽입, 삭제, 치환의 수를 교정된 텍스트의 길이로 나눈 값입니다. CER은 단어 오류율의 문자 수준 형태이며, 편집 횟수는 Levenshtein 거리입니다. Potato 2.12.1은 text_edit에서 전사자 간 일치도를 계산하지 않으므로, 두 전사자 사이의 CER이 일치도 수치를 대신합니다. JSONL 내보내기에는 각 줄에 대한 각 전사자의 텍스트가 있습니다.
python -m potato.export -c config.yaml -f jsonl -o export/각 레코드는 줄의 답변을 edited_text, original_text, edit_distance_chars, edit_distance_words가 있는 JSON 문자열로 저장합니다. 아래 스크립트는 Kraken의 텍스트를 각 전사자의 텍스트와 비교하고, 두 전사자를 서로 비교하며, Kraken의 오류를 Kraken의 신뢰도별로 나눕니다. 스크립트는 htr-correction/에서 실행하세요.
import collections
import json
def distance(a, b):
"""Levenshtein distance between two strings, in characters."""
previous = list(range(len(b) + 1))
for i, char_a in enumerate(a, 1):
current = [i]
for j, char_b in enumerate(b, 1):
current.append(min(previous[j] + 1, current[j - 1] + 1,
previous[j - 1] + (char_a != char_b)))
previous = current
return previous[-1]
def cer(pairs):
"""Character error rate of the first text in each pair against the second."""
errors = sum(distance(hyp, ref) for hyp, ref in pairs)
return errors / sum(len(ref) for _, ref in pairs), errors
# The HTR text and Kraken's confidence for every line
lines = {}
for row in open("data/htr.jsonl"):
item = json.loads(row)
lines[item["id"]] = item
# Each transcriber's corrected text for each line
corrected = collections.defaultdict(dict)
for row in open("export/annotations.jsonl"):
record = json.loads(row)
blob = record["labels"].get("transcription", {}).get("transcription")
if blob:
corrected[record["user_id"]][record["instance_id"]] = json.loads(blob)["edited_text"]
for user, texts in sorted(corrected.items()):
rate, errors = cer([(lines[i]["htr_text"], texts[i]) for i in texts])
print(f"HTR against {user}: CER {rate:.3f} ({errors} edits, {len(texts)} lines)")
first, second = sorted(corrected)
shared = sorted(set(corrected[first]) & set(corrected[second]))
rate, errors = cer([(corrected[second][i], corrected[first][i]) for i in shared])
differ = [i for i in shared if corrected[first][i] != corrected[second][i]]
print(f"{second} against {first}: CER {rate:.3f} ({errors} edits, {len(differ)} of {len(shared)} lines differ)")
# How wrong the HTR text was at each confidence level, against the first transcriber
bands = collections.defaultdict(list)
for i in shared:
confidence = float(lines[i].get("htr_confidence", 0))
band = "0.95 and up" if confidence >= 0.95 else "0.90 to 0.95" if confidence >= 0.9 else "below 0.90"
bands[band].append((lines[i]["htr_text"], corrected[first][i]))
for band in ("below 0.90", "0.90 to 0.95", "0.95 and up"):
rate, errors = cer(bands[band])
print(f"confidence {band:13} {len(bands[band]):3} lines CER {rate:.3f}")HTR against alice: CER 0.087 (150 edits, 88 lines)
HTR against bob: CER 0.106 (184 edits, 88 lines)
bob against alice: CER 0.021 (37 edits, 29 of 88 lines differ)
confidence below 0.90 15 lines CER 0.255
confidence 0.90 to 0.95 21 lines CER 0.140
confidence 0.95 and up 52 lines CER 0.042Kraken의 텍스트는 Alice의 교정에 대해 CER 8.7%, Bob의 교정에 대해 10.6%였습니다. 스크립트의 150번과 ketos test의 151번은 하나 차이가 나는데, Kraken의 줄 하나가 공백으로 끝나며 두 방식이 공백을 다르게 다루기 때문입니다. 두 전사자 사이의 37번의 편집은 Bob이 빠뜨린 37개의 갈고리이며, 규약만으로 모델의 측정 CER이 1.9포인트 달라졌습니다. 첫 줄을 교정하기 전에 규약에 합의하고, CER은 한 전사자의 텍스트나 두 전사자가 함께 확정한 텍스트에 대해 보고하세요.
신뢰도 0.90 미만의 줄은 CER이 25.5%였고, 0.95 이상의 줄은 4.2%였습니다. 한 줄에는 신뢰도가 없는데, Kraken이 글자 하나만 있는 줄에서 아무것도 읽지 못했기 때문이며, 스크립트는 이 줄을 0.90 미만으로 셉니다. 그렇다 해도 0.95 이상의 줄 52개 중 24개에 오류가 하나 이상 있었습니다. 신뢰도가 높은 줄을 건너뛰는 프로젝트는 그 오류를 학습 데이터에 남깁니다.
교정된 텍스트에서 TEI 만들기
Potato 2.13.1에는 text_edit 프로젝트에서 TEI로 가는 경로가 없습니다. tei 내보내기는 스팬 어노테이션을 TEI 이름 요소로 쓰며, 이 프로젝트에서는 내보내기가 오류와 함께 멈춥니다.
Export failed!
ERROR: Cannot export: No span annotations to exporttei 내보내기는 또한 교정된 텍스트가 아니라 각 항목의 원본 텍스트를 쓰므로, 설정에 스팬 스킴을 추가해도 여전히 Kraken의 판독이 나옵니다. 따라서 교정된 줄에서 TEI를 얻으려면 Potato 밖의 변환이 필요합니다. Transkribus 도움말 센터는 Scholar, Team, Organisation 요금제에 TEI 내보내기가 있다고 안내하며, 두 스타일시트 중 하나로 page2tei를 듭니다. page2tei 스타일시트는 MIT 라이선스이며 Transkribus 밖에서 실행됩니다. 이 스타일시트는 Transkribus 내보내기의 METS 파일에서 시작하는데, Potato의 내보내기는 PAGE 파일을 쓰고 METS 파일은 쓰지 않습니다. 일반 읽기 텍스트라면 짧은 스크립트가 PAGE 파일에서 교정된 TextLine을 읽기 순서대로 읽고 각 줄 앞에 TEI <lb/>를 쓸 수 있습니다.
HTR 교정을 위한 다른 도구
Transkribus와 eScriptorium은 각각 교정 편집기가 있으며, 두 플랫폼은 텍스트를 인식하고 모델을 학습하기도 합니다. Transkribus는 READ-COOP이 운영하는 호스팅 서비스입니다. Transkribus 요금제 페이지는 페이지당 1크레딧의 인식, 월 50크레딧과 PAGE XML을 포함한 기본 내보내기가 있는 무료 요금제, 연 €99에 900크레딧과 모든 내보내기 형식을 주는 Scholar 요금제를 나열합니다. eScriptorium은 MIT 라이선스이며, 프로젝트가 호스팅하는 서버나 기관의 인스턴스에서 Kraken을 실행하고, PAGE XML, ALTO, 일반 텍스트로 내보냅니다. 한 사람이 재학습 전에 모델의 출력을 교정한다면, 모델을 실행한 도구의 편집기가 더 짧은 경로입니다.
Potato는 여러 전사자가 교정을 나누어 맡을 때, 각 줄이 두 명 이상의 전사자에게 갈 때, 페이지 이미지가 자신의 서버에 남아야 할 때 알맞습니다. 디지털 인문학 전사 도구 비교는 Transkribus, eScriptorium, FromThePage, Zooniverse, Potato를 나란히 놓고 비교합니다.
HTR 교정에서 Potato의 한계
- 인식이나 학습 없음. Potato는 Transkribus, eScriptorium, OCR-D, Kraken의 PAGE XML을 읽으며 HTR 모델을 실행하지 않습니다.
- 전사에 대한 일치도 수치 없음. Potato 2.13.1은
text_edit답변에 대한 일치도를 계산하지 않으며, 위의 CER 스크립트가 그 대용입니다. - ALTO 없음. Potato 2.13.1은 PAGE XML을 읽고 쓰며 ALTO XML은 다루지 않습니다. 가져오기를 위해 Transkribus나 eScriptorium에서 PAGE XML을 내보내세요.
- 교정된 줄에서 TEI 없음. 위의 TEI 절이 설명하듯
tei내보내기는 원본 텍스트 위의 스팬을 씁니다. - 전체 단어 diff. "Changes" 보기는 전체 단어를 표시하며, "Chars changed"가 글자 수를 줍니다.
다음으로 볼 자료
- 디지털 인문학 형식, 모든 PAGE XML 가져오기 및 내보내기 옵션과
image_region디스플레이 옵션 - Potato의 디지털 인문학, HTR 교정, IIIF, TEI 워크플로를 한곳에
- 디지털 인문학 전사 도구 비교, Transkribus, eScriptorium, FromThePage, Zooniverse를 Potato와 나란히
- 프로젝트 가져오기,
potato import가 읽는 다른 형식 - 내보내기 형식, 각 내보내기가 쓰는 파일
참고문헌
- Sánchez, J. A., Romero, V., Toselli, A. H., & Vidal, E. (2016). ICFHR2016 Competition on Handwritten Text Recognition on the READ Dataset. 15th International Conference on Frontiers in Handwriting Recognition (ICFHR), 630-635. https://doi.org/10.1109/ICFHR.2016.0120
- Toselli, A. H., Romero, V., Villegas, M., Vidal, E., & Sánchez, J. A. (2018). HTR Dataset ICFHR 2016 (Version 1.2.0). Zenodo. https://doi.org/10.5281/zenodo.1297399
- Weil, S. (2023). HTR model for German manuscripts trained from several datasets. Zenodo. https://doi.org/10.5281/zenodo.7933463