Files
Copies/copienator/orientation.py
T

108 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Source-page provenance and versioned orientation reports."""
from collections import defaultdict
from pathlib import Path
import re
from copienator.annotation_layout import fingerprint
from copienator.json_io import atomic_write_json, read_json
def provenance_path(workspace, stem):
return workspace.metadata_dir / 'page_origins' / f'{stem}.json'
def report_path(workspace, stem):
return workspace.metadata_dir / 'orientation' / f'{stem}.json'
def save_provenance(workspace, pdf, pages):
atomic_write_json(provenance_path(workspace, pdf.stem), {
'version': 1, 'sha256': fingerprint(pdf), 'pages': pages,
})
def load_provenance(workspace, pdf, count):
try:
data = read_json(provenance_path(workspace, pdf.stem), default={})
pages = data.get('pages', [])
valid = (data.get('sha256') == fingerprint(pdf) and isinstance(pages, list)
and len(pages) == count and all(isinstance(p, dict)
and type(p.get('origin')) is int and p['origin'] > 0 for p in pages))
except (OSError, ValueError, TypeError, AttributeError):
return None
return pages if valid else None
def reports(workspace):
result = {}
for path in sorted((workspace.metadata_dir / 'orientation').glob('*.json')):
data = read_json(path)
pdf = workspace.copies_dir / f'{path.stem}.pdf'
if pdf.is_file() and fingerprint(pdf) in {data.get('sha256'), data.get('previous_sha256')}:
if any(data.get('images', {}).values()) or data.get('pending') or data.get('warning'):
result[pdf.name] = data
return result
def record_detection(workspace, image, columns):
match = re.fullmatch(r'(.+)_(\d+)', image.stem)
if not match:
if columns:
raise ValueError('Cannot map orientation columns to a copy')
return False
stem, part = match[1], int(match[2]) - 1
pdf = workspace.copies_dir / f'{stem}.pdf'
if not columns and not report_path(workspace, stem).exists():
return False
schema = read_json(workspace.cutleft_dir / f'{stem}_schema.json')
counts = schema['columns_per_file']
if not 0 <= part < len(counts) or any(type(c) is not int or not 1 <= c <= counts[part] for c in columns):
raise ValueError('Invalid upside-down column numbers')
digest = fingerprint(pdf)
if schema.get('source_sha256', digest) != digest:
raise ValueError('Cutleft images are stale; regenerate them before detecting orientation')
data = read_json(report_path(workspace, stem), default={})
if data.get('sha256') != digest:
data = {'version': 1, 'sha256': digest, 'images': {}}
pages = sorted({sum(counts[:part]) + c for c in columns})
data.setdefault('images', {})[image.name] = pages
atomic_write_json(report_path(workspace, stem), data)
if pages:
print(f'[Orientation] {pdf.name}: pages à l’envers : {pages}', flush=True)
return bool(pages)
def origin_groups(provenance, count):
groups = defaultdict(list)
for index in range(count):
groups[provenance[index]['origin'] if provenance else index].append(index + 1)
return list(groups.values())
def resolve_unknowns(verdict, groups):
"""Only known votes count; ties/all-unknown remain unresolved."""
result = dict(verdict)
for pages in groups:
up = sum(result[p] == 'upright' for p in pages)
down = sum(result[p] == 'upside_down' for p in pages)
majority = 'upright' if up > down else 'upside_down' if down > up else 'unknown'
for page in pages:
if result[page] == 'unknown':
result[page] = majority
return result
def incoherent(verdict, pages):
return {'upright', 'upside_down'} <= {verdict[p] for p in pages}
def repair_plan(verdict, groups):
"""Output slots refer to input pages; keep scale and retained content intact."""
order = list(range(1, len(verdict) + 1))
for pages in groups:
if all(verdict[p] == 'upside_down' for p in pages):
# A 180-degree turn reverses both A3 halves and secondary strips.
for slot, source in zip(pages, reversed(pages)):
order[slot - 1] = source
return order, {p for p, value in verdict.items() if value == 'upside_down'}