Data Structures

Document tree

Page represents one image. Its children may contain words, lines, blocks, tables, images or custom elements. There is no required tree depth: a detector can return flat regions, and a layout stage can organize them later.

Each element has one parent. The sequence of children defines reading order; there is no separate order field. Assign links through parent or children after constructing the objects.

        graph TD
    Element --> Page
    Element --> TextRegion
    Element --> Table
    Element --> Cell
    Element --> Image
    TextRegion --> Types["type: word / line / block"]
    

The diagram shows class inheritance. Tree containment is defined separately by parent-child links.

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"

Assigning parent moves the element between containers. Cycles are not allowed; Page must remain the root.

Elements and fields

All classes inherit from Element and share these fields:

Field

Meaning

type

Nonempty structural type; also used as the COCO category name.

label

Optional model-specific class name.

polygon

Optional list of at least four finite XY vertices in original-image pixels.

detection_confidence

Optional detection confidence in [0, 1].

orientation

Optional absolute clockwise angle in degrees. None means unknown; zero means upright. The angle must be finite.

Geometry is not calculated from children automatically. Changing a tree link also does not change coordinates or orientation.

Class

Additional fields

Page

Positive image_width and image_height, and optional file_name (empty string by default). Its type is page.

TextRegion

Type word, line or block; optional text and recognition_confidence in [0, 1]. Text can be stored at any of these levels.

Table

Type table; optional positive rows and columns.

Cell

Type cell; zero-based row and column; positive row_span and column_span, both 1 by default.

Image

Type image for an image region within a page.

Field validation also applies to assignment. Call page.validate_tree() after editing to check contextual rules, including overlapping table cells and cells extending beyond declared grid dimensions. Incomplete grids are allowed.

Tables

Table cells are linked like other elements. Their indices describe the grid; their children can contain text regions.

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()

Copying and serialization

to_tree_dict() produces a nested dictionary of fields and ordered children, without parent references. Element.from_tree_dict() restores the registered classes and parent links. Page.clone() creates independent objects:

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 files

Page.to_dict(), save() and load() use single-image polygon COCO with Manuscript extensions. The public to_coco() and from_coco() functions provide the same conversion.

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")

The format preserves text, geometry and tree links. Import also supports unversioned polygon COCO with one image. RLE masks and multiple polygons per annotation are unsupported; a bbox alone does not supply polygon geometry.

Text output

Page.get_text(format="plain") serializes text without changing the tree. Pipeline.get_text(page, ...) delegates to this method.

Words use spaces, lines use newlines, and blocks and tables use blank lines. Between unlike siblings, the higher-priority boundary wins. Text from descendants takes precedence over text stored on their parent, preventing duplicate output. Flat words remain a single line: serialization does not infer lines from geometry. Separators are inserted only between nonempty results.

text = page.get_text()
compact = page.get_text(separators={"line": " / ", "block": "\n"})

Use separator_priorities to change boundary precedence; higher values win. Defaults are word=0, line=1, block=2 and table=2.

Tables serialize as row-major TSV, preserving empty grid positions. Spanned cells appear only at their origin; covered slots remain empty. Whitespace inside cells becomes spaces, so cell text cannot introduce extra table rows. Only plain output is currently supported.

Custom elements

A generic Element(type="custom_region") can retain a custom type and the common fields. During import or cloning, unknown types produce a warning and remain generic elements. Specialized pipeline stages may skip them.

Register a subclass when a type needs additional fields or behavior:

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

Register custom classes before restoring their serialized data. Fields not declared by the selected class are rejected.

class manuscript.data.Element(*, type, label=None, polygon=None, detection_confidence=None, orientation=None)[source]

Bases: BaseModel, NodeMixin

A positioned or geometry-free document node.

type defines structural semantics and the exported COCO category. label stores an optional model-specific class name. Polygon and confidence are optional; geometry is not inferred from children. orientation is an absolute clockwise angle in degrees, with None meaning unknown and zero upright.

Parameters:
type: str
label: str | None
polygon: List[Tuple[float, float]] | None
detection_confidence: float | None
orientation: float | None
classmethod resolve_type(kind)[source]

Use registered semantics or preserve an unfamiliar structural type.

Return type:

Type[Element]

Parameters:

kind (str)

validate_tree()[source]

Check context-dependent rules after construction or editing.

Geometry is never inferred from children. Empty containers and detached elements are allowed. Table grid checks do not require a complete grid.

Return type:

None

classmethod register(kind, node_class)[source]
Return type:

None

Parameters:
to_tree_dict()[source]

Serialize node fields and ordered children without parent cycles.

Return type:

dict

classmethod from_tree_dict(data)[source]

Restore typed nodes and parent links from a nested tree dictionary.

Return type:

Element

Parameters:

data (dict)

class manuscript.data.Page(*, type='page', label=None, polygon=None, detection_confidence=None, orientation=None, image_width, image_height, file_name='')[source]

Bases: Element

Root for one image; children may be flat regions or nested structures.

Parameters:
type: Literal['page']
image_width: int
image_height: int
file_name: str
clone()[source]

Copy data and rebuild parent links within the copied tree.

to_dict()[source]
get_text(format='plain', *, separators=None, separator_priorities=None)[source]

Serialize text with type boundaries and row-major plain-text tables.

save(path)[source]
classmethod load(path)[source]
class manuscript.data.TextRegion(*, type, label=None, polygon=None, detection_confidence=None, orientation=None, text=None, recognition_confidence=None)[source]

Bases: Element

Word, line or block, with independently stored recognition results.

Parameters:
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)[source]

Bases: Element

Table with optional grid dimensions and cell children.

Parameters:
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)[source]

Bases: Element

Cell at a zero-based grid position, optionally spanning rows or columns.

Parameters:
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)[source]

Bases: Element

Parameters:
type: Literal['image']
manuscript.data.to_coco(page)[source]

Single-page COCO with lossless Manuscript extensions.

IDs belong to this export. Missing geometry remains an empty segmentation, not a fabricated polygon; such objects are containers, not training targets.

Return type:

dict

Parameters:

page (Page)

manuscript.data.from_coco(data)[source]

Read one page from Manuscript COCO or unversioned polygon COCO.

Unversioned input uses category names as types, optional parent_id links and value as text. Empty values mean unknown text. Annotation sequence determines sibling order. Known category names select typed elements; unknown names create generic Elements with a warning. RLE masks and multiple polygons are unsupported. Bbox alone does not supply a polygon.

Return type:

Page

Parameters:

data (dict)