Support for post annotation label reassignement.

This commit is contained in:
sebastien committed 2026-10-11 19:03:04 +02:00
1 parent 698ac4240c
commit 370f3252ef
10 files changed
+242 -36

No files matched your search

+19 -5
View File
@@ -9,20 +9,26 @@ import numpy as np
from pdf2image import convert_from_path
from PIL import Image, ImageChops, ImageDraw, ImageFilter
from copienator.commands import annotating
from copienator import configuration, utils
from copienator import (
EvaluationWorkspace,
ExitCode,
atomic_write_json,
configuration,
evaluation_parser,
execute,
read_json,
utils,
workspace_from_args,
)
from copienator.annotation_actions import apply_checkbox_actions, apply_score_overrides
from copienator.answer_info import build_answer_info
from copienator.annotation_data import AnnotationData, load_annotation_data
from copienator.annotation_modifications import (
BASELINE,
output_removals,
stage_baseline,
)
from copienator.answer_info import build_answer_info
from copienator.commands import annotating
from copienator.filesystem import staged_files
from copienator.return_answers import save_return_answer_options
@@ -168,6 +174,8 @@ def apply_actions_and_regenerate(
scores = dict.fromkeys(all_labels, "")
answer_labels: list[str] = []
dirty_images: dict[str, Image.Image] = {}
header_heights: dict[str, int] = {}
filtered_labels: list[str] = []
concatenated: list[Image.Image] = []
filtered: list[Image.Image] = []
incomplete = False
@@ -221,15 +229,18 @@ def apply_actions_and_regenerate(
final_image.paste(body, (0, new_header_height), mask=body)
dirty_images[label] = final_image
header_heights[label] = new_header_height
answer_labels.append(label)
concatenated.append(final_image)
if float(scores[label]) != 4.0 or result.get("feedback", []):
filtered.append(final_image)
filtered_labels.append(label)
if incomplete:
return ExitCode.PARTIAL
concat_image = concatenate(concatenated)
filtered_image = concatenate(filtered)
with staged_files(output_dir, remove=("Concat.jpg", "Concat_F.jpg", "Concat_F.pdf",
"touched.json", "answer_labels.json")) as staging:
with staged_files(output_dir, remove=(*output_removals(output_dir), BASELINE)) as staging:
for label, image in dirty_images.items():
image.save(staging / f"{label}.jpg")
if not preserve_score_file:
@@ -241,6 +252,9 @@ def apply_actions_and_regenerate(
concat_image.save(staging / "Concat.jpg")
if filtered_image is not None:
filtered_image.save(staging / "Concat_F.jpg")
stage_baseline(workspace.root, "Bnot", output_dir, staging, dirty_images,
header_heights, build_answer_info(scores, labels_data, answer_labels),
filtered_labels, scores)
if preserve_score_file:
print(f" Preserved existing score.json in {output_dir}")
@@ -9,11 +9,11 @@ from typing import Any
from PIL import Image, ImageDraw
from copienator import configuration
from copienator import (
EvaluationWorkspace,
ExitCode,
atomic_write_json,
configuration,
evaluation_parser,
execute,
read_json,
@@ -21,8 +21,15 @@ from copienator import (
workspace_from_args,
)
from copienator.annotation_actions import apply_checkbox_actions, apply_score_overrides
from copienator.answer_info import build_answer_info
from copienator.annotation_data import AnnotationData, RefaireList, load_annotation_data
from copienator.annotation_modifications import (
BASELINE,
baseline,
load_image,
output_removals,
stage_baseline,
)
from copienator.answer_info import build_answer_info
from copienator.commands import annotating
from copienator.commands.reading_annotations import (
concatenate,
@@ -247,6 +254,9 @@ def apply_actions_and_regenerate_grouped(
touched = dict.fromkeys(all_labels, False)
answer_labels: list[str] = []
dirty_images: dict[str, Image.Image] = {}
header_heights: dict[str, int] = {}
filtered_labels: list[str] = []
previous_baseline = baseline(output_dir) if (output_dir / BASELINE).is_file() else None
concat_images: list[Image.Image] = []
filtered_groups: list[list[Image.Image]] = []
incomplete = False
@@ -266,9 +276,19 @@ def apply_actions_and_regenerate_grouped(
if result.get("error") == "empty-answer":
continue
saved_image = output_dir / f"{label}.jpg"
if selected_labels and label not in selected_labels and saved_image.is_file():
with Image.open(saved_image) as saved:
final_image = saved.convert("RGB").copy()
baseline_entry = (previous_baseline or {}).get("images", {}).get(label)
if selected_labels and label not in selected_labels and (saved_image.is_file() or baseline_entry):
if annotation_dir in {"BGnot", "Bnot"}:
if previous_baseline is not None and label in previous_baseline["images"]:
entry = previous_baseline["images"][label]
final_image = load_image(output_dir, entry)
header_heights[label] = entry["header_height"]
else:
with Image.open(saved_image) as saved:
final_image = saved.convert("RGB").copy()
else:
with Image.open(saved_image) as saved:
final_image = saved.convert("RGB").copy()
if (
simple_annotated is not None
and label in simple_layout["images"]
@@ -278,10 +298,11 @@ def apply_actions_and_regenerate_grouped(
final_image = simple_annotated.crop(
(0, hmin, simple_annotated.width, hmax)
)
dirty_images[label] = final_image
dirty_images[label] = final_image
scores[label] = str(old_scores.get(label, scores[label]))
concat_images.append(final_image)
answer_labels.append(label)
filtered_labels.append(label)
# Keep previously reviewed content, including handwriting.
if annotation_dir == "BGnot":
extras = get_extra_pdfs_as_images(
@@ -327,6 +348,7 @@ def apply_actions_and_regenerate_grouped(
# Persist every final block, including unchanged answers, for returns.
dirty_images[label] = final_image
header_heights[label] = new_header_height
answer_labels.append(label)
concat_images.append(final_image)
@@ -335,6 +357,7 @@ def apply_actions_and_regenerate_grouped(
feedback.get("to_delete", False) for feedback in feedbacks
)
if not perfect or has_notes:
filtered_labels.append(label)
extras = (
get_extra_pdfs_as_images(workspace.root, label, annotating, all_labels)
if annotation_dir == "BGnot"
@@ -346,8 +369,7 @@ def apply_actions_and_regenerate_grouped(
concat_image = concatenate(concat_images)
if incomplete:
return ExitCode.PARTIAL, "\n".join(logs)
with staged_files(output_dir, remove=("Concat.jpg", "Concat_F.pdf", "Concat_F.jpg",
"touched.json", "answer_labels.json")) as staging:
with staged_files(output_dir, remove=(*output_removals(output_dir), BASELINE)) as staging:
if simple_layout is not None:
simple_layout["replaced"] = sorted(
set(simple_layout["replaced"]) | selected_labels
@@ -370,6 +392,9 @@ def apply_actions_and_regenerate_grouped(
[image for group in filtered_groups for image in group]
)
filtered_image.save(staging / "Concat_F.jpg")
stage_baseline(workspace.root, annotation_dir, output_dir, staging, dirty_images,
header_heights, build_answer_info(scores, labels_data, answer_labels, touched),
filtered_labels, scores)
if preserve_score_file:
logs.append(f" Preserved existing score.json in {output_dir}")
logs.append(f" Saved regenerated files in {output_dir}")