File size: 2,043 Bytes
97b64b9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""Pydantic schema for template annotation files (``annotations/<template_id>.json``)."""

from __future__ import annotations

from pydantic import BaseModel, ConfigDict, Field, model_validator


class SignatureBoxAnnotation(BaseModel):
    """A single signature field on a page, in pixel coordinates of the template image."""

    model_config = ConfigDict(extra="forbid")

    id: str
    x1: float
    y1: float
    x2: float
    y2: float
    role: str = "Signer"
    name: str = "Signer"

    @model_validator(mode="after")
    def _normalize_box(self) -> "SignatureBoxAnnotation":
        x1, x2 = sorted((self.x1, self.x2))
        y1, y2 = sorted((self.y1, self.y2))
        self.x1, self.x2, self.y1, self.y2 = x1, x2, y1, y2
        if x2 <= x1 or y2 <= y1:
            raise ValueError(f"Signature box '{self.id}' has non-positive area: {self.as_tuple()}")
        return self

    def as_tuple(self) -> tuple[float, float, float, float]:
        """Return the box as ``(x1, y1, x2, y2)``."""
        return (self.x1, self.y1, self.x2, self.y2)

    @property
    def width(self) -> float:
        return self.x2 - self.x1

    @property
    def height(self) -> float:
        return self.y2 - self.y1


class TemplatePageAnnotation(BaseModel):
    """A single page of a template document."""

    model_config = ConfigDict(extra="forbid")

    page: int = 1
    page_size: tuple[int, int]
    dpi: int = 300
    signature_boxes: list[SignatureBoxAnnotation] = Field(default_factory=list)


class TemplateAnnotation(BaseModel):
    """Full annotation for one template document (possibly multi-page)."""

    model_config = ConfigDict(extra="forbid")

    id: str
    source_image: "str | None" = None
    document_type: "str | None" = None
    pages: list[TemplatePageAnnotation]
    auto_detected: bool = False

    def page_by_number(self, page: int) -> TemplatePageAnnotation:
        for p in self.pages:
            if p.page == page:
                return p
        raise KeyError(f"Template '{self.id}' has no page {page}")