Schemas
Box, Image, Document, Entity and DetectorOutput — the five shapes every stage reads and writes.
All five are plain interfaces with create* factories, exported from the root.
They match ScaleDP's schemas field for field.
Box
interface Box {
text: string
score: number
x: number
y: number
width: number
height: number
angle: number
}Not xyxy, not a polygon
x/y is the top-left of the axis-aligned box of the same size centred on
the rotated rect's centre. angle is degrees about that centre, normalised to
(-90, 270]. width is always the longer side, regardless of orientation.
That convention is ScaleDP's (scaledp/schemas/Box.py) and is preserved exactly,
because the parity tests diff against goldens generated by running the Python
code. It means an axis-aligned box behaves as you expect and a rotated one needs
boxPoints-style handling rather than strokeRect.
import { bbox, shape, isRotated, boxFromPolygon, mergeOverlappingBoxes } from '@stabrise/scaledp'
isRotated(box) // |angle| >= 3 degrees
bbox(box, padding) // [x0, y0, x1, y1]
shape(box, padding) // [[x0, y0], [x1, y1]]boxIou compares axis-aligned envelopes and ignores angle. mergeBoxes joins
text with a space, takes the minimum score, and resets angle to 0 — merging
two rotated boxes cannot preserve a single rotation.
ScaleDpImage
interface ScaleDpImage {
path: string
resolution: number // DPI; 0 means unknown
data: Uint8Array // encoded bytes, not pixels
imageType: 'png' | 'webp' | 'jpeg'
exception: string
height: number
width: number
}data holds encoded bytes. DataToImage passes them through without
re-encoding and probes the dimensions, so wrapping a 40 MB scan costs a header
read rather than a full decode.
Document
interface Document {
path: string
text: string
type: string // 'text' | 'ocr' | 'pdf' | 'tesseract' | 'tesseract-recognizer'
bboxes: Box[]
exception: string
}type records which engine produced it, which is how you tell an OCR result
from a lifted PDF text layer downstream. mergeDocuments(self, other) joins
text with a newline, argument-first, matching Python.
DetectorOutput
interface DetectorOutput {
path: string
type: string // 'paddle' | 'dbnet-onnx' | 'yolo' | 'signature' | 'face'
bboxes: Box[]
exception: string
}A Document and a DetectorOutput both carry bboxes, which is why stages that
consume boxes accept either. The difference is that a Document also has text.
Entity and NerOutput
interface Entity {
entity_group: string
score: number
word: string
start: number // character offsets into Document.text
end: number
boxes: Box[]
}
interface NerOutput {
path: string
entities: Entity[]
exception: string
json: string // JSON.stringify(entities)
}start/end index the document's text, which is exactly what visualizeNer
splices on. boxes are mapped back from Document.bboxes through a
character-to-box map built from the real text — see
the divergence note on why
that matters.
Factories
import { createBox, createImage, createDocument, createNerOutput, createDetectorOutput } from '@stabrise/scaledp'Each takes a Partial and fills the rest with the schema's zero values —
path: 'memory', empty strings, empty arrays. They are what a stage's onError
returns, and what you want when hand-building a row.