Структуры данных
Дерево документа
Page описывает одно изображение. Внутри него могут находиться слова, строки, блоки, таблицы, изображения и пользовательские элементы. Глубина дерева не ограничена: детектор может вернуть плоский список регионов, а компонент анализа структуры — сгруппировать их позднее.
У каждого элемента один родитель — parent. Порядок чтения определяется положением в children; отдельного поля order нет. Родительские связи задаются через parent или children после создания объектов.
graph TD
Element --> Page
Element --> TextRegion
Element --> Table
Element --> Cell
Element --> Image
TextRegion --> Types["тип: word / line / block"]
Схема показывает наследование классов. Вложенность элементов в дереве не зависит от наследования и задаётся родительскими связями.
from manuscript.data import Page, TextRegion
page = Page(image_width=1000, image_height=1500)
line = TextRegion(
type="line",
polygon=[(10, 10), (400, 10), (400, 50), (10, 50)],
)
line.parent = page
line.children = [
TextRegion(type="word", text="Hello"),
TextRegion(type="word", text="world"),
]
assert line.children[0].parent is line
assert page.get_text() == "Hello world"
При смене parent элемент переносится к новому родителю. Циклы запрещены, а Page всегда остаётся корнем дерева.
Элементы и поля
Все классы наследуются от Element и имеют общие поля:
Поле |
Назначение |
|---|---|
|
Тип элемента. Не может быть пустым; при экспорте в COCO становится именем категории. |
|
Необязательное имя класса конкретной модели. |
|
Полигон: не менее четырёх вершин XY в пикселях исходного изображения. Координаты должны быть конечными числами. Поле необязательное. |
|
Уверенность детектора от 0 до 1. Поле необязательное. |
|
Угол поворота по часовой стрелке в градусах. |
Геометрия родителя не рассчитывается по дочерним элементам автоматически. При изменении связей в дереве координаты и ориентация остаются прежними.
Класс |
Дополнительные поля |
|---|---|
|
Положительные |
|
Тип |
|
Тип |
|
Тип |
|
Тип |
Значения полей проверяются при каждом присваивании. После изменения дерева вызовите page.validate_tree(): метод проверит связи между элементами, пересечения ячеек и выход за размеры таблицы. Заполнять всю сетку таблицы необязательно.
Таблицы
Ячейки включаются в дерево так же, как остальные элементы. Индексы задают их положение в таблице, а текст хранится в дочерних текстовых регионах.
from manuscript.data import Table, Cell
table = Table(rows=1, columns=2)
cell = Cell(row=0, column=0, column_span=2)
cell.children = [TextRegion(type="word", text="Heading")]
table.children = [cell]
table.parent = page
page.validate_tree()
Копирование и сериализация
to_tree_dict() сохраняет поля и дочерние элементы во вложенный словарь без обратных ссылок на родителей. Порядок элементов сохраняется. Element.from_tree_dict() восстанавливает классы и связи дерева, а Page.clone() создаёт независимую копию:
copied = page.clone()
assert copied is not page
assert copied.to_tree_dict() == page.to_tree_dict()
tree = page.to_tree_dict()
restored_tree = Page.from_tree_dict(tree)
assert restored_tree.to_tree_dict() == tree
Файлы COCO
Page.to_dict(), save() и load() используют полигональный COCO для одного изображения с расширениями Manuscript. Публичные функции to_coco() и from_coco() выполняют те же преобразования.
from manuscript.data import to_coco, from_coco
coco = to_coco(page)
restored = from_coco(coco)
assert restored.to_tree_dict() == page.to_tree_dict()
page.save("page.json")
restored = Page.load("page.json")
Формат сохраняет текст, геометрию и связи дерева. Можно также импортировать полигональный COCO для одного изображения без маркера версии. RLE-маски и несколько полигонов в одной аннотации не поддерживаются. Если указан только bbox, полигон по нему не создаётся.
Вывод текста
Page.get_text(format="plain") возвращает текст, не меняя дерево. Pipeline.get_text(page, ...) вызывает тот же метод.
Слова разделяются пробелами, строки — переводами строк, блоки и таблицы — пустыми строками. Между соседними элементами разных типов используется разделитель с более высоким приоритетом. Чтобы текст не дублировался, текст дочерних элементов имеет приоритет перед текстом родителя. Слова из плоского списка выводятся одной строкой: метод не группирует их по геометрии. Разделители добавляются только между непустыми фрагментами.
text = page.get_text()
compact = page.get_text(separators={"line": " / ", "block": "\n"})
separator_priorities задаёт приоритет разделителей: чем больше значение, тем выше приоритет. По умолчанию word=0, line=1, block=2 и table=2.
Таблицы выводятся построчно в формате TSV, включая пустые ячейки. Текст объединённой ячейки записывается только в её начальной позиции; остальные занятые ею позиции остаются пустыми. Пробельные символы внутри ячеек заменяются обычными пробелами, чтобы не нарушать строки и столбцы TSV. Пока поддерживается только формат plain.
Пользовательские элементы
Обычный Element(type="custom_region") может хранить пользовательский тип и общие поля. При импорте или клонировании неизвестные типы вызывают предупреждение и остаются обычными элементами. Специализированные этапы обработки могут их пропускать.
Зарегистрируйте подкласс, если типу нужны дополнительные поля или поведение:
from typing import Literal
from manuscript.data import Element
class Stamp(Element):
type: Literal["stamp"] = "stamp"
inscription: str = ""
Element.register("stamp", Stamp)
stamp = Stamp(inscription="Archive")
stamp.parent = page
Зарегистрируйте пользовательские классы до загрузки сохранённых данных. Если данные содержат поля, которых нет в выбранном классе, возникнет ошибка.
- class manuscript.data.Element(*, type, label=None, polygon=None, detection_confidence=None, orientation=None)[исходный код]
Базовые классы:
BaseModel,NodeMixinУзел документа с геометрией или без неё.
type определяет тип элемента и имя категории при экспорте в COCO. label — необязательное имя класса модели. Полигон и уверенность тоже необязательны; геометрия не рассчитывается по дочерним элементам. orientation задаёт угол поворота по часовой стрелке в градусах: None — ориентация неизвестна, 0 — поворота нет.
- Параметры:
- classmethod resolve_type(kind)[исходный код]
Выбирает зарегистрированный класс элемента или сохраняет неизвестный тип без преобразования.
- validate_tree()[исходный код]
Проверяет связи и согласованность элементов после создания или изменения дерева.
Геометрия не рассчитывается по дочерним элементам. Допускаются пустые контейнеры, элементы без родителя и таблицы с незаполненными ячейками.
- Тип результата:
- classmethod register(kind, node_class)[исходный код]
- to_tree_dict()[исходный код]
Сохраняет поля элемента и его потомков в порядке дерева, без обратных ссылок на родителей.
- Тип результата:
- classmethod from_tree_dict(data)[исходный код]
Восстанавливает элементы соответствующих классов и связи между ними из вложенного словаря.
- class manuscript.data.Page(*, type='page', label=None, polygon=None, detection_confidence=None, orientation=None, image_width, image_height, file_name='')[исходный код]
Базовые классы:
ElementКорневой элемент одного изображения. Может содержать плоский список регионов или вложенные элементы.
- Параметры:
- clone()[исходный код]
Создаёт копию дерева с собственными данными и родительскими связями.
- to_dict()[исходный код]
- get_text(format='plain', *, separators=None, separator_priorities=None)[исходный код]
Собирает текст с разделителями для разных типов регионов и выводит таблицы по строкам.
- save(path)[исходный код]
- classmethod load(path)[исходный код]
- class manuscript.data.TextRegion(*, type, label=None, polygon=None, detection_confidence=None, orientation=None, text=None, recognition_confidence=None)[исходный код]
Базовые классы:
ElementСлово, строка или блок с собственным результатом распознавания.
- Параметры:
- class manuscript.data.Table(*, type='table', label=None, polygon=None, detection_confidence=None, orientation=None, rows=None, columns=None)[исходный код]
Базовые классы:
ElementТаблица с необязательными размерами сетки и дочерними ячейками.
- Параметры:
- class manuscript.data.Cell(*, type='cell', label=None, polygon=None, detection_confidence=None, orientation=None, row, column, row_span=1, column_span=1)[исходный код]
Базовые классы:
ElementЯчейка с индексами от нуля, при необходимости занимающая несколько строк или столбцов.
- Параметры:
- class manuscript.data.Image(*, type='image', label=None, polygon=None, detection_confidence=None, orientation=None)[исходный код]
Базовые классы:
Element- Параметры:
- manuscript.data.to_coco(page)[исходный код]
COCO для одной страницы с расширениями Manuscript без потери данных.
При каждом экспорте идентификаторы назначаются заново. Если геометрия не задана, сегментация остаётся пустой: полигон не создаётся автоматически. Такие объекты служат контейнерами и не подходят в качестве обучающих примеров.
- manuscript.data.from_coco(data)[исходный код]
Прочитать одну страницу из Manuscript COCO или полигонального COCO без версии.
В COCO без маркера версии типы элементов определяются по именам категорий, связи — по необязательному parent_id, а текст берётся из value. Пустое значение означает, что текст неизвестен. Порядок аннотаций задаёт порядок соседних элементов. Для известных категорий создаются элементы соответствующих классов, для неизвестных — обычный Element с предупреждением. RLE-маски и несколько полигонов не поддерживаются. По одному bbox полигон не создаётся.