Структуры данных

Дерево документа

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 и имеют общие поля:

Поле

Назначение

type

Тип элемента. Не может быть пустым; при экспорте в COCO становится именем категории.

label

Необязательное имя класса конкретной модели.

polygon

Полигон: не менее четырёх вершин XY в пикселях исходного изображения. Координаты должны быть конечными числами. Поле необязательное.

detection_confidence

Уверенность детектора от 0 до 1. Поле необязательное.

orientation

Угол поворота по часовой стрелке в градусах. None — ориентация неизвестна, 0 — поворота нет. Угол должен быть конечным числом. Поле необязательное.

Геометрия родителя не рассчитывается по дочерним элементам автоматически. При изменении связей в дереве координаты и ориентация остаются прежними.

Класс

Дополнительные поля

Page

Положительные image_width и image_height, необязательное file_name (по умолчанию пустая строка). Тип — page.

TextRegion

Тип word, line или block; необязательные text и recognition_confidence в диапазоне [0, 1]. Текст можно хранить на любом из этих уровней.

Table

Тип table; необязательные положительные rows и columns.

Cell

Тип cell; row и column с отсчётом от нуля; положительные row_span и column_span, оба по умолчанию равны 1.

Image

Тип image для области изображения внутри страницы.

Значения полей проверяются при каждом присваивании. После изменения дерева вызовите 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 — поворота нет.

Параметры:
type: str
label: str | None
polygon: List[Tuple[float, float]] | None
detection_confidence: float | None
orientation: float | None
classmethod resolve_type(kind)[исходный код]

Выбирает зарегистрированный класс элемента или сохраняет неизвестный тип без преобразования.

Тип результата:

Type[Element]

Параметры:

kind (str)

validate_tree()[исходный код]

Проверяет связи и согласованность элементов после создания или изменения дерева.

Геометрия не рассчитывается по дочерним элементам. Допускаются пустые контейнеры, элементы без родителя и таблицы с незаполненными ячейками.

Тип результата:

None

classmethod register(kind, node_class)[исходный код]
Тип результата:

None

Параметры:
to_tree_dict()[исходный код]

Сохраняет поля элемента и его потомков в порядке дерева, без обратных ссылок на родителей.

Тип результата:

dict

classmethod from_tree_dict(data)[исходный код]

Восстанавливает элементы соответствующих классов и связи между ними из вложенного словаря.

Тип результата:

Element

Параметры:

data (dict)

class manuscript.data.Page(*, type='page', label=None, polygon=None, detection_confidence=None, orientation=None, image_width, image_height, file_name='')[исходный код]

Базовые классы: Element

Корневой элемент одного изображения. Может содержать плоский список регионов или вложенные элементы.

Параметры:
type: Literal['page']
image_width: int
image_height: int
file_name: str
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

Слово, строка или блок с собственным результатом распознавания.

Параметры:
type: Literal['word', 'line', 'block']
text: str | None
recognition_confidence: float | None
class manuscript.data.Table(*, type='table', label=None, polygon=None, detection_confidence=None, orientation=None, rows=None, columns=None)[исходный код]

Базовые классы: Element

Таблица с необязательными размерами сетки и дочерними ячейками.

Параметры:
type: Literal['table']
rows: int | None
columns: int | None
class manuscript.data.Cell(*, type='cell', label=None, polygon=None, detection_confidence=None, orientation=None, row, column, row_span=1, column_span=1)[исходный код]

Базовые классы: Element

Ячейка с индексами от нуля, при необходимости занимающая несколько строк или столбцов.

Параметры:
type: Literal['cell']
row: int
column: int
row_span: int
column_span: int
class manuscript.data.Image(*, type='image', label=None, polygon=None, detection_confidence=None, orientation=None)[исходный код]

Базовые классы: Element

Параметры:
type: Literal['image']
manuscript.data.to_coco(page)[исходный код]

COCO для одной страницы с расширениями Manuscript без потери данных.

При каждом экспорте идентификаторы назначаются заново. Если геометрия не задана, сегментация остаётся пустой: полигон не создаётся автоматически. Такие объекты служат контейнерами и не подходят в качестве обучающих примеров.

Тип результата:

dict

Параметры:

page (Page)

manuscript.data.from_coco(data)[исходный код]

Прочитать одну страницу из Manuscript COCO или полигонального COCO без версии.

В COCO без маркера версии типы элементов определяются по именам категорий, связи — по необязательному parent_id, а текст берётся из value. Пустое значение означает, что текст неизвестен. Порядок аннотаций задаёт порядок соседних элементов. Для известных категорий создаются элементы соответствующих классов, для неизвестных — обычный Element с предупреждением. RLE-маски и несколько полигонов не поддерживаются. По одному bbox полигон не создаётся.

Тип результата:

Page

Параметры:

data (dict)