File size: 6,615 Bytes
39b5db1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""Regenerate the bundled sample PDF and the README figure.

    python scripts/make_assets.py

Writes:
  assets/sample.pdf          2-page synthetic A4 document (text, table, chart)
  assets/figure_overview.png source | partition | adjacency | exploded fragments

The figure is rendered from a real `generate_dataset` run on the sample with
the parameters below, so it shows exactly what the tool produces.
"""
from __future__ import annotations

import os
import sys

import numpy as np
from PIL import Image, ImageDraw

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT)

from src.inspection import (  # noqa: E402
    _font,
    make_preview,
    reassemble,
    render_adjacency,
    render_partition,
)
from src.pipeline import generate_dataset  # noqa: E402

SAMPLE = os.path.join(ROOT, "assets", "sample.pdf")
FIGURE = os.path.join(ROOT, "assets", "figure_overview.png")
PARAMS = {"dpi": 100, "n_pieces": 14, "noise_strength": 28.0, "noise_scale": 96.0,
          "master_seed": 7, "lossy": False}

PARAGRAPH = (
    "Fragments recovered from the archive box were numbered in the order they "
    "were lifted, not in reading order. Each was photographed on a neutral "
    "background before any attempt at reconstruction. Edge profiles, ruling "
    "lines and ink density were logged per fragment so candidate joins can be "
    "scored independently of content."
)


def make_sample_pdf(path: str) -> None:
    import fitz

    doc = fitz.open()
    # Page 1: prose + table.
    page = doc.new_page(width=595, height=842)
    page.insert_text((56, 72), "Sample document - Dataset-Maker", fontsize=17, fontname="helv")
    page.insert_text((56, 90), "Synthetic test page. Contents are placeholder text.",
                     fontsize=9, fontname="helv", color=(0.35, 0.35, 0.35))
    page.draw_line((56, 100), (539, 100), color=(0, 0, 0), width=0.7)
    y = 116
    for n in range(5):
        page.insert_text((56, y + 10), f"{n + 1}. Observation", fontsize=10.5, fontname="hebo")
        page.insert_textbox(fitz.Rect(56, y + 16, 539, y + 86), PARAGRAPH,
                            fontsize=9.5, fontname="tiro")
        y += 92
    top = 600
    cols = ["Fragment", "Width (mm)", "Height (mm)", "Edge type"]
    rows = [["F-01", "62", "88", "torn"], ["F-02", "45", "71", "torn / cut"],
            ["F-03", "80", "39", "torn"], ["F-04", "58", "66", "folded"],
            ["F-05", "33", "52", "torn"]]
    for r, row in enumerate([cols] + rows):
        for c, cell in enumerate(row):
            rect = fitz.Rect(56 + c * 121, top + r * 22, 56 + (c + 1) * 121, top + (r + 1) * 22)
            page.draw_rect(rect, color=(0, 0, 0), width=0.5,
                           fill=(0.92, 0.92, 0.9) if r == 0 else None)
            page.insert_textbox(rect + (5, 6, 0, 0), cell, fontsize=8.5,
                                fontname="hebo" if r == 0 else "cour")
    page.insert_text((56, 760), "Table 1. Fragment dimensions (placeholder values).",
                     fontsize=8.5, fontname="tiro")

    # Page 2: line chart + ruled notes area.
    page = doc.new_page(width=595, height=842)
    page.insert_text((56, 72), "Figure page", fontsize=15, fontname="helv")
    x0, y0, x1, y1 = 80, 110, 520, 400
    page.draw_rect(fitz.Rect(x0, y0, x1, y1), color=(0, 0, 0), width=0.8)
    for k in range(1, 6):
        yy = y0 + k * (y1 - y0) / 6
        page.draw_line((x0, yy), (x1, yy), color=(0.8, 0.8, 0.8), width=0.4)
    rng = np.random.default_rng(3)
    for series, colour in ((0, (0.1, 0.3, 0.8)), (1, (0.8, 0.2, 0.1))):
        vals = np.cumsum(rng.normal(0, 1, 30)) * 12 + (y0 + y1) / 2 + series * 40
        pts = [(x0 + i * (x1 - x0) / 29, float(np.clip(v, y0 + 5, y1 - 5)))
               for i, v in enumerate(vals)]
        for a, b in zip(pts, pts[1:]):
            page.draw_line(a, b, color=colour, width=1.4)
    page.insert_text((80, 420), "Figure 1. Two synthetic series (random walk).",
                     fontsize=8.5, fontname="tiro")
    for k in range(14):
        yy = 470 + k * 24
        page.draw_line((56, yy), (539, yy), color=(0.55, 0.65, 0.85), width=0.5)
    page.insert_text((60, 490), "Notes:", fontsize=10, fontname="hebo")

    doc.set_metadata({"producer": "Dataset-Maker scripts/make_assets.py",
                      "creationDate": "D:20240101000000Z", "modDate": "D:20240101000000Z"})
    doc.save(path, garbage=4, deflate=True, no_new_id=True)
    doc.close()


def exploded(torn, spread: float = 0.22) -> np.ndarray:
    """Fragments pushed outward from the page centre, on a dark canvas."""
    H, W = torn.height, torn.width
    pad = int(max(H, W) * spread)
    canvas = np.full((H + 2 * pad, W + 2 * pad, 3), 40, dtype=np.uint8)
    cx, cy = W / 2, H / 2
    for p in torn.pieces:
        h, w = p.mask.shape
        px, py = p.x + w / 2, p.y + h / 2
        dx = int((px - cx) * spread * 1.6) + pad
        dy = int((py - cy) * spread * 1.6) + pad
        region = canvas[p.y + dy:p.y + dy + h, p.x + dx:p.x + dx + w]
        region[p.mask] = p.rgb[p.mask]
    return canvas


def panel(img: np.ndarray, title: str, height: int) -> Image.Image:
    im = Image.fromarray(img)
    im = im.resize((round(im.width * height / im.height), height), Image.LANCZOS)
    out = Image.new("RGB", (im.width, height + 34), (255, 255, 255))
    out.paste(im, (0, 34))
    ImageDraw.Draw(out).rectangle((0, 34, im.width - 1, height + 33), outline=(200, 200, 204))
    ImageDraw.Draw(out).text((2, 8), title, fill=(24, 24, 27), font=_font(17))
    return out


def main() -> None:
    make_sample_pdf(SAMPLE)
    run = generate_dataset(SAMPLE, source_name="sample.pdf", **PARAMS)
    torn = run.pages[0]
    pv = make_preview(torn, max_side=900)
    n = len(torn.pieces)
    panels = [
        panel(reassemble(torn), "1  source page (A4 render)", 620),
        panel(render_partition(pv, n), "2  partition + piece index", 620),
        panel(render_adjacency(pv, n, torn.adjacency), "3  adjacency ground truth", 620),
        panel(exploded(torn), "4  fragments, pulled apart from their offsets", 620),
    ]
    gap = 18
    fig = Image.new("RGB", (sum(p.width for p in panels) + gap * 3, panels[0].height),
                    (255, 255, 255))
    x = 0
    for p in panels:
        fig.paste(p, (x, 0))
        x += p.width + gap
    fig.save(FIGURE, optimize=True)
    r = run.reports[0]
    print(f"wrote {SAMPLE} and {FIGURE}")
    print(f"page 0: {r.n_pieces} fragments, {r.n_edges} adjacency pairs, "
          f"partition={'ok' if r.is_partition else 'FAIL'}; params={PARAMS}")


if __name__ == "__main__":
    main()