Initial commit for finalisation web interface
This commit is contained in:
1 parent
b295f871a7
commit
c36642e8de
33 files changed
+2745
-138
No files matched your search
@@ -0,0 +1,71 @@
|
||||
"""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
|
||||
Reference in new issue
Block a user