72 lines
3.3 KiB
Python
72 lines
3.3 KiB
Python
"""Rendering inputs captured after handwriting interpretation, before user overrides.
|
|
|
|
Optional for the legacy readers. The web renderer consumes this state without
|
|
rescanning annotation PDFs. A selective redo merges only the selected labels.
|
|
"""
|
|
from copy import deepcopy
|
|
from hashlib import sha256
|
|
import json
|
|
from pathlib import Path
|
|
from PIL import Image
|
|
from copienator import atomic_write_json, read_json
|
|
|
|
FILENAME = 'review_snapshot.json'
|
|
|
|
|
|
def stage(output, staging, data, notes, *, engine, bnote=None, selected=None, force_filtered=()):
|
|
previous = read_json(output / FILENAME, default=None)
|
|
if selected and previous is None:
|
|
# Old preserved images cannot tell us their original feedback and ink.
|
|
return
|
|
if previous and previous['engine'] == 'standard' and engine == 'grouped':
|
|
geometry = {item['label']: item for item in previous['bnote']['images']}
|
|
for label, note in previous['notes'].items():
|
|
note['old_header_h'] = geometry.get(label, {}).get('header_height', 0)
|
|
if 'layout' in geometry.get(label, {}):
|
|
note['layout'] = geometry[label]['layout']
|
|
retained = set(force_filtered)
|
|
if selected:
|
|
base = read_json(output / 'annotation_baseline.json', default={})
|
|
retained.update(set(base.get('filtered', [])) - set(selected))
|
|
selected = set(selected or data)
|
|
if previous:
|
|
# Preserve raw inputs for the labels not re-read in a selective redo.
|
|
merged_data = previous['data'] if selected != set(data) else {}
|
|
merged_notes = previous['notes'] if selected != set(data) else {}
|
|
else:
|
|
merged_data, merged_notes = {}, {}
|
|
for label in selected:
|
|
if label in data:
|
|
content = deepcopy(data[label])
|
|
path = Path(content['pdf_path']).resolve()
|
|
root = output.parent.parent.resolve()
|
|
content['pdf_path'] = str(path.relative_to(root)) if path.is_relative_to(root) else str(path)
|
|
merged_data[label] = content
|
|
merged_notes.pop(label, None)
|
|
note = notes.get(label)
|
|
if note is not None:
|
|
filename = '.review-note-' + sha256(label.encode()).hexdigest() + '.png'
|
|
note['img'].save(staging / filename)
|
|
merged_notes[label] = {k: v for k, v in note.items() if k != 'img'} | {'file': filename}
|
|
payload = {'version': 1, 'engine': engine, 'data': merged_data, 'notes': merged_notes, 'bnote': bnote, 'force_filtered': sorted(retained)}
|
|
# Roundtrip normalizes tuples while rejecting unsupported rendering data.
|
|
atomic_write_json(staging / FILENAME, json.loads(json.dumps(payload)))
|
|
|
|
|
|
def load(root, output):
|
|
saved = read_json(output / FILENAME)
|
|
if saved.get('version') != 1:
|
|
raise ValueError('Version du résultat de lecture inconnue. Relisez les annotations.')
|
|
data = saved['data']
|
|
for content in data.values():
|
|
path = Path(content['pdf_path'])
|
|
content['pdf_path'] = path if path.is_absolute() else root / path
|
|
notes = {}
|
|
for label, metadata in saved['notes'].items():
|
|
filename = metadata['file']
|
|
if Path(filename).name != filename:
|
|
raise ValueError('Image manuscrite invalide.')
|
|
with Image.open(output / filename) as image:
|
|
notes[label] = {k: v for k, v in metadata.items() if k != 'file'} | {'img': image.copy()}
|
|
return saved, data, notes
|