Compare commits
2
Commits
bf05272797
...
359c62004c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
359c62004c | ||
|
|
13a08f6cbd |
@@ -340,6 +340,34 @@ def group_images(image_files: list[Path]) -> dict[str, list[Path]]:
|
|||||||
return dict(groups)
|
return dict(groups)
|
||||||
|
|
||||||
|
|
||||||
|
def sort_boxes_for_image(
|
||||||
|
workspace: EvaluationWorkspace,
|
||||||
|
image_file: Path,
|
||||||
|
boxes: list[BoxItem],
|
||||||
|
) -> list[BoxItem]:
|
||||||
|
"""Sort boxes in the image's page-column reading order when schema exists."""
|
||||||
|
match = re.match(r"(.+)_(\d+)$", image_file.stem)
|
||||||
|
if not match:
|
||||||
|
return boxes
|
||||||
|
schema_path = workspace.cutleft_dir / f"{match.group(1)}_schema.json"
|
||||||
|
try:
|
||||||
|
schema = read_json(schema_path)
|
||||||
|
columns_per_file = schema["columns_per_file"]
|
||||||
|
column_count = int(columns_per_file[int(match.group(2)) - 1])
|
||||||
|
if column_count < 1:
|
||||||
|
return boxes
|
||||||
|
except (OSError, KeyError, IndexError, TypeError, ValueError):
|
||||||
|
return boxes
|
||||||
|
|
||||||
|
def position(item: BoxItem) -> tuple[int, int, int]:
|
||||||
|
ymin, xmin, _ymax, xmax = item.box_2d
|
||||||
|
center_x = (xmin + xmax) // 2
|
||||||
|
column = min(column_count - 1, max(0, center_x * column_count // 1000))
|
||||||
|
return column, ymin, xmin
|
||||||
|
|
||||||
|
return sorted(boxes, key=position)
|
||||||
|
|
||||||
|
|
||||||
def _existing_context(output_json: Path) -> list[str]:
|
def _existing_context(output_json: Path) -> list[str]:
|
||||||
try:
|
try:
|
||||||
loaded = read_json(output_json)
|
loaded = read_json(output_json)
|
||||||
@@ -452,6 +480,9 @@ def process_copy_group(
|
|||||||
continue
|
continue
|
||||||
annotation.name = "Unknown"
|
annotation.name = "Unknown"
|
||||||
|
|
||||||
|
annotation.list = sort_boxes_for_image(
|
||||||
|
workspace, image_file, annotation.list
|
||||||
|
)
|
||||||
atomic_write_json(output_json, annotation.model_dump())
|
atomic_write_json(output_json, annotation.model_dump())
|
||||||
accumulated_labels.extend(box.label for box in annotation.list)
|
accumulated_labels.extend(box.label for box in annotation.list)
|
||||||
generated += 1
|
generated += 1
|
||||||
|
|||||||
@@ -333,28 +333,16 @@ class ImageViewer:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
current_data = read_json(self.current_json_path)
|
current_data = read_json(self.current_json_path)
|
||||||
nb_pages = self.current_meta["schema"]["columns_per_file"][
|
items = current_data["list"]
|
||||||
self.current_meta["part"] - 1
|
|
||||||
]
|
|
||||||
original_items = current_data["list"]
|
|
||||||
ordered_items = sort_bounding_boxes(original_items, nb_pages)
|
|
||||||
|
|
||||||
if ordered_items != original_items:
|
|
||||||
current_data["list"] = ordered_items
|
|
||||||
atomic_write_json(self.current_json_path, current_data)
|
|
||||||
print(
|
|
||||||
f"Reordered labels by column in "
|
|
||||||
f"{self.current_json_path.name}."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Perform the conversion now, post-edit
|
# Perform the conversion now, post-edit
|
||||||
converted_items = convert_list(
|
converted_items = convert_list(
|
||||||
ordered_items,
|
items,
|
||||||
self.current_meta["part"],
|
self.current_meta["part"],
|
||||||
self.current_meta["schema"]
|
self.current_meta["schema"]
|
||||||
)
|
)
|
||||||
|
|
||||||
labels = normalized_labels(ordered_items)
|
labels = normalized_labels(items)
|
||||||
false_labels = [
|
false_labels = [
|
||||||
label for label in labels if label not in self.valid_labels
|
label for label in labels if label not in self.valid_labels
|
||||||
]
|
]
|
||||||
|
|||||||
+4
-3
@@ -19,13 +19,14 @@ FINAL_SCORE_FONT_PATH = None
|
|||||||
MODEL_LITE_ID = "gemini-3.5-flash-lite"
|
MODEL_LITE_ID = "gemini-3.5-flash-lite"
|
||||||
|
|
||||||
# Modèle pour identifier visuellement les labels
|
# Modèle pour identifier visuellement les labels
|
||||||
MODEL_FOR_LABEL_ID = "gemini-3.5-flash-lite"
|
# MODEL_FOR_LABEL_ID = "gemini-3.5-flash-lite" # 3.5 flash lite marche moyennement, il insiste pour mettre les labels dans l'ordre, sans considérer ce qu'il y a écrit
|
||||||
|
MODEL_FOR_LABEL_ID = "gemini-3.8-flash"
|
||||||
|
|
||||||
# Modèle pour des choses normales
|
# Modèle pour des choses normales
|
||||||
MODEL_FLASH_ID = "gemini-3.6-flash"
|
MODEL_FLASH_ID = "gemini-3.8-flash"
|
||||||
|
|
||||||
# Modèle pour des choses dures
|
# Modèle pour des choses dures
|
||||||
MODEL_PRO_ID = "gemini-3.6-flash"
|
MODEL_PRO_ID = "gemini-3.8-flash"
|
||||||
# MODEL_PRO_ID = "gemini-3.1-pro-preview"
|
# MODEL_PRO_ID = "gemini-3.1-pro-preview"
|
||||||
|
|
||||||
PAGE_SPLITTER_KB = {
|
PAGE_SPLITTER_KB = {
|
||||||
|
|||||||
@@ -910,6 +910,39 @@ class StandardCliTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertIsNone(viewer.accumulated_results)
|
self.assertIsNone(viewer.accumulated_results)
|
||||||
|
|
||||||
|
def test_plotting_validation_preserves_manually_edited_order(self) -> None:
|
||||||
|
module = self.modules["plotting"]
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
json_path = Path(directory) / "Copie01_01.json"
|
||||||
|
manually_ordered = [
|
||||||
|
{"box_2d": [100, 600, 130, 700], "label": "Ex 2"},
|
||||||
|
{"box_2d": [500, 100, 530, 200], "label": "Ex 1"},
|
||||||
|
]
|
||||||
|
atomic_write_json(
|
||||||
|
json_path,
|
||||||
|
{"name": "Student", "list": manually_ordered},
|
||||||
|
)
|
||||||
|
viewer = module.ImageViewer.__new__(module.ImageViewer)
|
||||||
|
viewer.is_viewing = True
|
||||||
|
viewer.current_json_path = json_path
|
||||||
|
viewer.current_meta = {
|
||||||
|
"schema": {"columns_per_file": [2]},
|
||||||
|
"part": 1,
|
||||||
|
}
|
||||||
|
viewer.valid_labels = {"Ex 1", "Ex 2"}
|
||||||
|
viewer.accumulated_results = {"name": "Student", "list": []}
|
||||||
|
viewer.history = []
|
||||||
|
viewer.current_pil_image = Mock()
|
||||||
|
viewer.label = Mock()
|
||||||
|
|
||||||
|
viewer.on_enter(None)
|
||||||
|
|
||||||
|
self.assertEqual(read_json(json_path)["list"], manually_ordered)
|
||||||
|
self.assertEqual(
|
||||||
|
[item["label"] for item in viewer.accumulated_results["list"]],
|
||||||
|
["Ex 2", "Ex 1"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_plotting_validates_plain_and_directional_labels(self) -> None:
|
def test_plotting_validates_plain_and_directional_labels(self) -> None:
|
||||||
module = self.modules["plotting"]
|
module = self.modules["plotting"]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -1008,6 +1041,50 @@ class StandardCliTests(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_label_detection_writes_boxes_in_column_reading_order(self) -> None:
|
||||||
|
module = self.modules["gemini_for_labels"]
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
evaluation = Path(directory) / "Exam"
|
||||||
|
copies = evaluation / "Copies"
|
||||||
|
cutleft = evaluation / "Cutleft"
|
||||||
|
copies.mkdir(parents=True)
|
||||||
|
cutleft.mkdir()
|
||||||
|
image = cutleft / "Copie03_02.jpg"
|
||||||
|
image.write_bytes(b"image")
|
||||||
|
atomic_write_json(
|
||||||
|
cutleft / "Copie03_schema.json",
|
||||||
|
{"columns_per_file": [2, 2]},
|
||||||
|
)
|
||||||
|
client = Mock()
|
||||||
|
client.models.generate_content.return_value = Mock(
|
||||||
|
text=(
|
||||||
|
'{"name":"Continued","list":['
|
||||||
|
'{"box_2d":[100,600,130,700],"label":"Ex 2"},'
|
||||||
|
'{"box_2d":[500,100,530,200],"label":"Ex 1"},'
|
||||||
|
'{"box_2d":[300,620,330,720],"label":"Ex 3"}]}'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
module.process_copy_group(
|
||||||
|
EvaluationWorkspace(evaluation),
|
||||||
|
"Copie03",
|
||||||
|
[image],
|
||||||
|
client=client,
|
||||||
|
labels_text="Ex 1\nEx 2\nEx 3\n",
|
||||||
|
names_text="Student\n",
|
||||||
|
valid_labels={"Ex 1", "Ex 2", "Ex 3"},
|
||||||
|
valid_names={"Student", "Unknown", "Continued"},
|
||||||
|
overwrite=True,
|
||||||
|
sleep=lambda _seconds: None,
|
||||||
|
target_interval=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = read_json(copies / "Copie03_02.json")
|
||||||
|
self.assertEqual(
|
||||||
|
[item["label"] for item in result["list"]],
|
||||||
|
["Ex 1", "Ex 2", "Ex 3"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_label_detection_retries_unknown_labels(self) -> None:
|
def test_label_detection_retries_unknown_labels(self) -> None:
|
||||||
module = self.modules["gemini_for_labels"]
|
module = self.modules["gemini_for_labels"]
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
|||||||
Reference in New Issue
Block a user