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 |
|---|---|
|
Nonempty structural type; also used as the COCO category name. |
|
Optional model-specific class name. |
|
Optional list of at least four finite XY vertices in original-image pixels. |
|
Optional detection confidence in [0, 1]. |
|
Optional absolute clockwise angle in degrees. |
Geometry is not calculated from children automatically. Changing a tree link also does not change coordinates or orientation.
Class |
Additional fields |
|---|---|
|
Positive |
|
Type |
|
Type |
|
Type |
|
Type |
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,NodeMixinA 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:
- classmethod resolve_type(kind)[source]
Use registered semantics or preserve an unfamiliar structural type.
- 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:
- class manuscript.data.Page(*, type='page', label=None, polygon=None, detection_confidence=None, orientation=None, image_width, image_height, file_name='')[source]
Bases:
ElementRoot for one image; children may be flat regions or nested structures.
- Parameters:
- class manuscript.data.TextRegion(*, type, label=None, polygon=None, detection_confidence=None, orientation=None, text=None, recognition_confidence=None)[source]
Bases:
ElementWord, line or block, with independently stored recognition results.
- Parameters:
- class manuscript.data.Table(*, type='table', label=None, polygon=None, detection_confidence=None, orientation=None, rows=None, columns=None)[source]
Bases:
ElementTable with optional grid dimensions and cell children.
- Parameters:
- 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:
ElementCell at a zero-based grid position, optionally spanning rows or columns.
- Parameters:
- class manuscript.data.Image(*, type='image', label=None, polygon=None, detection_confidence=None, orientation=None)[source]
Bases:
Element- Parameters:
- 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.
- 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.