Use per-page annotation spacing with inferred margin fallback
This commit is contained in:
1 parent
b069805988
commit
0bb6f7d97c
10 files changed
+170
-39
No files matched your search
@@ -63,9 +63,13 @@ def make_base_image(pdf_path):
|
||||
base_img = Image.new("RGBA", (max_w, total_h), "white")
|
||||
|
||||
current_y = 0
|
||||
page_regions = []
|
||||
for page in pages:
|
||||
base_img.paste(page.convert("RGBA"), ((max_w - page.width) // 2, current_y))
|
||||
x = (max_w - page.width) // 2
|
||||
base_img.paste(page.convert("RGBA"), (x, current_y))
|
||||
page_regions.append({'x': x, 'y': current_y, 'width': page.width, 'height': page.height})
|
||||
current_y += page.height
|
||||
base_img.info['page_regions'] = page_regions
|
||||
return (base_img, total_h, max_w)
|
||||
|
||||
def normalize_mathtext(text):
|
||||
@@ -352,6 +356,10 @@ def compose_label_image(base_img, label, result, hmin,
|
||||
|
||||
layout = dict(layout if layout is not None else layout_for(source_pdf))
|
||||
margin_left = layout['margin_left']
|
||||
page_regions = base_img.info.get('page_regions', [
|
||||
{'x': 0, 'y': 0, 'width': base_img.width, 'height': base_img.height}])
|
||||
page_margins = layout.get('page_margins', [])
|
||||
per_page = layout.get('version', 1) >= 2
|
||||
left_pad = 0
|
||||
if base_img.width < TARGET_MIN_WIDTH:
|
||||
total_missing = TARGET_MIN_WIDTH - base_img.width
|
||||
@@ -411,7 +419,11 @@ def compose_label_image(base_img, label, result, hmin,
|
||||
total_height = base_img.height + header_height
|
||||
|
||||
# Create Canvas
|
||||
final_img = Image.new("RGB", (base_img.width + margin_left, total_height), "white")
|
||||
final_width = (max(TARGET_MIN_WIDTH + margin_left,
|
||||
max(region['width'] + (page_margins[i] if i < len(page_margins) else margin_left)
|
||||
for i, region in enumerate(page_regions)))
|
||||
if per_page else base_img.width + margin_left)
|
||||
final_img = Image.new("RGB", (final_width, total_height), "white")
|
||||
|
||||
# Draw Headers
|
||||
current_y = 0
|
||||
@@ -434,7 +446,13 @@ def compose_label_image(base_img, label, result, hmin,
|
||||
|
||||
# Paste Base Image
|
||||
image_offset_y = current_y
|
||||
final_img.paste(base_img, (margin_left, image_offset_y))
|
||||
if per_page:
|
||||
for i, region in enumerate(page_regions):
|
||||
x, y, w, h = (region[key] for key in ('x', 'y', 'width', 'height'))
|
||||
margin = page_margins[i] if i < len(page_margins) else margin_left
|
||||
final_img.paste(base_img.crop((x, y, x + w, y + h)), (margin, image_offset_y + y))
|
||||
else:
|
||||
final_img.paste(base_img, (margin_left, image_offset_y))
|
||||
|
||||
# 2. Draw Local Annotations
|
||||
draw = ImageDraw.Draw(final_img, "RGBA") # Refresh draw object
|
||||
@@ -451,12 +469,24 @@ def compose_label_image(base_img, label, result, hmin,
|
||||
|
||||
# Draw Rectangle (if not suppressed)
|
||||
if "norectangle" not in fb:
|
||||
draw.rectangle([target_xmin, target_ymin, target_xmax, target_ymax], outline="red", width=3)
|
||||
boxes = [[target_xmin, target_ymin, target_xmax, target_ymax]]
|
||||
if per_page:
|
||||
boxes = []
|
||||
for i, region in enumerate(page_regions):
|
||||
top = max(ymin - hmin, region['y'])
|
||||
bottom = min(ymax - hmin, region['y'] + region['height'])
|
||||
if bottom <= top:
|
||||
continue
|
||||
margin = page_margins[i] if i < len(page_margins) else margin_left
|
||||
shift = margin - region['x']
|
||||
boxes.append([xmin + shift, top + image_offset_y,
|
||||
xmax + shift, bottom + image_offset_y])
|
||||
for box in boxes:
|
||||
draw.rectangle(box, outline="red", width=3)
|
||||
|
||||
if draw_callback:
|
||||
callback_items.append(("local_rect",
|
||||
{"box": [target_xmin, target_ymin, target_xmax, target_ymax]},
|
||||
{"data": fb, "index": idx}))
|
||||
if draw_callback:
|
||||
callback_items.append(("local_rect", {"box": box},
|
||||
{"data": fb, "index": idx}))
|
||||
|
||||
# Render Text
|
||||
txt_img = render_fn(fb['text'], width_px=ANNOT_WIDTH,
|
||||
|
||||
@@ -428,6 +428,11 @@ class ImageReviewer:
|
||||
for group in analysis['groups'])
|
||||
margin_info = (f"Auto margin: {offsets} mm from left"
|
||||
+ (' | Check uncertain pages' if needs_review else '') + '\n')
|
||||
for page in analysis.get('pages', []):
|
||||
if page.get('status') == 'unknown':
|
||||
value = page.get('effective_edge_mm')
|
||||
fallback = f" (copy average: {value:.1f} mm)" if value is not None else ''
|
||||
margin_info += f"Page {page['page']}: unknown margin{fallback}\n"
|
||||
if analysis.get('no_margin_from_page') is not None:
|
||||
margin_info += f"No margin from page {analysis['no_margin_from_page']} onwards\n"
|
||||
self.label_info.configure(
|
||||
|
||||
@@ -23,6 +23,7 @@ from copienator import (
|
||||
)
|
||||
from copienator.filesystem import staged_directory
|
||||
from copienator.annotation_layout import fingerprint, metadata_path, save_parts
|
||||
from copienator.left_margin import margins_with_fallback
|
||||
|
||||
SQUARE = 1000 // 38
|
||||
ANSWER_TOP_PADDING_POINTS = 4 * 72 / 25.4
|
||||
@@ -133,7 +134,7 @@ def _render_split_outputs(
|
||||
provenance: defaultdict[str, list[dict]] = defaultdict(list)
|
||||
schema = read_json(input_pdf.parent.parent / 'Cutleft' / f'{input_pdf.stem}_schema.json', default={})
|
||||
analysis = schema.get('auto_margin') or {}
|
||||
measurements = ({item['page']: item for item in analysis.get('pages', [])}
|
||||
measurements = ({item['page']: item for item in margins_with_fallback(analysis.get('pages', []))}
|
||||
if schema.get('source_sha256') == fingerprint(input_pdf) else {})
|
||||
with tempfile.TemporaryDirectory(prefix="copienator-split-") as temp_directory:
|
||||
temporary = Path(temp_directory)
|
||||
@@ -206,13 +207,14 @@ def _render_split_outputs(
|
||||
part_path,
|
||||
)
|
||||
parts_by_label[clean_label].append(part_path)
|
||||
edge = measurements.get(page_number + 1, {}).get('edge_mm')
|
||||
measurement = measurements.get(page_number + 1, {})
|
||||
edge = measurement.get('effective_edge_mm')
|
||||
remaining = None if edge is None else max(0, edge - clip.x0 * 25.4 / 72)
|
||||
provenance[clean_label].append({
|
||||
'source': input_pdf.name, 'source_sha256': schema.get('source_sha256'),
|
||||
'page': page_number + 1, 'crop_points': list(clip),
|
||||
'margin_mm': remaining,
|
||||
'margin_status': ('unknown' if remaining is None else
|
||||
'margin_status': ('unknown' if measurement.get('edge_mm') is None else
|
||||
'low' if remaining < 20 else 'normal'),
|
||||
})
|
||||
|
||||
|
||||
Reference in new issue
Block a user