Automatic orientation issues detection and fix, experimental

This commit is contained in:
sebastien committed 2026-10-13 22:27:41 +02:00
1 parent 78a7b7679c
commit 46845c4612
12 files changed
+683 -9

No files matched your search

+24 -3
View File
@@ -17,6 +17,7 @@ from pydantic import BaseModel, Field, model_validator
from copienator import configuration as config
from copienator.gemini_config import fresh_seed, thinking_config
from copienator.copy_errors import marked_copy_paths
from copienator.orientation import record_detection, reports as orientation_reports
from copienator import (
CliError,
EvaluationWorkspace,
@@ -149,6 +150,10 @@ class BoxItem(BaseModel):
label: str = Field(description="The label associated with the specific box")
class AnnotationData(BaseModel):
upside_down_pages: typing.List[typing.Annotated[int, Field(strict=True, ge=1)]] = Field(
default_factory=list,
description='1-based column numbers in this image that appear upside down (180 degrees)',
)
error: typing.Literal["insufficient_crop"] | None = Field(
default=None, description="Report insufficient_crop when the crop hides information needed to identify labels"
)
@@ -160,7 +165,7 @@ class AnnotationData(BaseModel):
@model_validator(mode="after")
def require_success_payload(self):
if self.error is None and not {"name", "list"} <= self.model_fields_set:
if self.error is None and not self.upside_down_pages and not {"name", "list"} <= self.model_fields_set:
raise ValueError("Successful detection must include name and list")
return self
@@ -286,6 +291,12 @@ students may skip questions. On success, return error=null.
text = text.replace("image of the left columns", "image of the full pages")
text += "\nThis image now contains full pages, in the same reading order. Identify the labels using this expanded view."
text += ('\nReport upside-down handwriting in upside_down_pages, using 1-based '
'column numbers in THIS image, left to right. Only report clear 180-degree '
'orientation errors; blank pages are not evidence. If any are reported, '
'you may return name="Unknown", list=[] and error=null. Orientation '
'reporting takes priority over insufficient_crop.')
contents = [
types.Content(
role="user",
@@ -514,6 +525,10 @@ def process_copy_group(
config=request_config,
)
annotation = AnnotationData.model_validate_json(response.text)
if record_detection(workspace, image_file, annotation.upside_down_pages):
output_json.unlink(missing_ok=True)
print(f'[{group_key}] Orientation à vérifier avant de valider les labels.')
break
if annotation.error == "insufficient_crop":
print(f"[{group_key}] {image_file.name}: insufficient crop: "
f"{annotation.error_reason or 'labels require a wider view'}")
@@ -573,7 +588,7 @@ def process_copy_group(
annotation.list = sort_boxes_for_image(
workspace, image_file, annotation.list
)
atomic_write_json(output_json, annotation.model_dump(exclude={"error", "error_reason"}))
atomic_write_json(output_json, annotation.model_dump(exclude={"error", "error_reason", "upside_down_pages"}))
accumulated_labels.extend(box.label for box in annotation.list)
generated += 1
print(f"[{group_key}] Saved {image_file.name} after {time.monotonic() - started:.0f}s.")
@@ -696,7 +711,13 @@ def run(
print("Incomplete images (rerun label detection to resume):")
for path in sorted(incomplete):
print(f" {path.name}")
return ExitCode.PARTIAL if warnings or incomplete else ExitCode.SUCCESS
orientation = {name: data for name, data in orientation_reports(workspace).items()
if Path(name).stem in groups}
for name, data in orientation.items():
pages = sorted({p for values in data.get('images', {}).values() for p in values})
if pages:
print(f'[Orientation] {name}: pages {pages}. Signalez la copie ou utilisez la correction automatique.')
return ExitCode.PARTIAL if warnings or incomplete or orientation else ExitCode.SUCCESS
def build_parser() -> argparse.ArgumentParser: