Support for post annotation label reassignement.
This commit is contained in:
1 parent
698ac4240c
commit
370f3252ef
10 files changed
+242
-36
No files matched your search
+22
-4
@@ -30,6 +30,7 @@ from .scores import ScoreEditorPanel
|
||||
from .notifications import notify_desktop
|
||||
from .syncthing import SyncthingPanel
|
||||
from .manual_resolution import ManualResolutionPanel
|
||||
from .annotation_modifications import AnnotationModificationsPanel, annotation_mode_for_reader
|
||||
from .recorrection import prepare_selection_file
|
||||
from .refaire import SECTION as REFAIRE_SECTION
|
||||
from .refaire import (
|
||||
@@ -824,6 +825,7 @@ class CopienatorApp(tk.Tk):
|
||||
"labels",
|
||||
"plotting",
|
||||
"manual_resolution",
|
||||
"modify_annotations",
|
||||
"giving_names",
|
||||
"edit_scores",
|
||||
"statement_persp",
|
||||
@@ -888,6 +890,9 @@ class CopienatorApp(tk.Tk):
|
||||
value = False # Deletion is a one-run choice, never a saved default.
|
||||
if step.id == 'statement' and spec.name == 'restart':
|
||||
value = False
|
||||
if step.id == "modify_annotations" and spec.name == "annotation_dir" and spec.name not in values:
|
||||
reader = self.state_store.step("read_annotations")
|
||||
value = annotation_mode_for_reader(reader)
|
||||
choices = spec.choices
|
||||
if spec.name == "annotation_dir" and step.id in {"export", "import"}:
|
||||
choices, value = self._annotation_directory_choices(step.id)
|
||||
@@ -939,13 +944,20 @@ class CopienatorApp(tk.Tk):
|
||||
self.edit_recorrection_button.grid(row=row, column=0, columnspan=2, sticky="w", pady=8)
|
||||
row += 1
|
||||
|
||||
if step.id == "manual_resolution":
|
||||
if step.id in {"manual_resolution", "modify_annotations"}:
|
||||
def manual_evaluation():
|
||||
target = self.arg_vars.get("target")
|
||||
value = target.get().strip() if target else ""
|
||||
return (self.repository / value).resolve() if value else self.evaluation
|
||||
|
||||
self.manual_panel = ManualResolutionPanel(self.form, manual_evaluation)
|
||||
if step.id == "modify_annotations":
|
||||
self.manual_panel = AnnotationModificationsPanel(
|
||||
self.form, manual_evaluation,
|
||||
lambda: self.arg_vars["annotation_dir"].get(),
|
||||
)
|
||||
self.arg_vars["annotation_dir"].trace_add("write", lambda *_: self.manual_panel.reload())
|
||||
else:
|
||||
self.manual_panel = ManualResolutionPanel(self.form, manual_evaluation)
|
||||
self.manual_panel.grid(row=row, column=0, columnspan=2, sticky="nsew", pady=8)
|
||||
if "target" in self.arg_vars:
|
||||
self.arg_vars["target"].trace_add("write", lambda *_args: self.manual_panel.reload())
|
||||
@@ -966,7 +978,7 @@ class CopienatorApp(tk.Tk):
|
||||
attach_tooltip(extra_entry, step.extra_arguments_help)
|
||||
row += 1
|
||||
|
||||
self.run_button.configure(text="Enregistrer la sélection" if step.id == "refaire_selection" else ("Marquer terminée" if step.is_manual else "Exécuter"))
|
||||
self.run_button.configure(text="Make modifications" if step.id == "modify_annotations" else "Enregistrer la sélection" if step.id == "refaire_selection" else ("Marquer terminée" if step.is_manual else "Exécuter"))
|
||||
if step.id == "personal_create":
|
||||
self.run_button.configure(text="Marquer Réussie")
|
||||
if step.id == "personal_read":
|
||||
@@ -1953,7 +1965,7 @@ class CopienatorApp(tk.Tk):
|
||||
self._append_console(f"Impossible de lancer la copie suivante : {exc}\n")
|
||||
status, return_code = "failed", 1
|
||||
self.pending_refaire_commands = []
|
||||
if step_id == "refaire_merge" and status == "success":
|
||||
if step_id in {"refaire_merge", "modify_annotations"} and status == "success":
|
||||
for downstream in ("giving_names", "update_ods", "final_score", "personal_deploy", "personal_sent"):
|
||||
entry = self.state_store.step(downstream)
|
||||
if entry.get("status") in {"success", "detected", "skipped"}:
|
||||
@@ -1995,6 +2007,12 @@ class CopienatorApp(tk.Tk):
|
||||
"manual": False, "reason": reason,
|
||||
})
|
||||
self._append_console(f"Remplacer les barèmes par Gemini : ignorée — {reason}\n")
|
||||
if step_id == "read_annotations" and status == "success":
|
||||
reader = self.state_store.step(step_id)
|
||||
mode = annotation_mode_for_reader(reader)
|
||||
values = dict(self.state_store.step("modify_annotations").get("values", {}))
|
||||
values["annotation_dir"] = mode
|
||||
self.state_store.update_step("modify_annotations", values=values)
|
||||
if step_id == "annotation" and status == "success":
|
||||
generation_variant = self.state_store.step("annotation").get(
|
||||
"last_run_variant"
|
||||
|
||||
@@ -45,21 +45,29 @@ def cut_operator(percent: float, keep: int, mode: str) -> str:
|
||||
|
||||
|
||||
class CutHelper(tk.Toplevel):
|
||||
def __init__(self, parent, path: Path, on_accept, initial=None):
|
||||
# Load the source before creating a window so invalid PDFs leave no dialog.
|
||||
with pymupdf.open(path) as document:
|
||||
if not len(document):
|
||||
raise ValueError("Le PDF est vide.")
|
||||
self.heights = [page.rect.height for page in document]
|
||||
width = min(850, parent.winfo_screenwidth() - 100)
|
||||
self.scale = min(1.5, width / max(page.rect.width for page in document))
|
||||
rendered = []
|
||||
for page in document:
|
||||
pix = page.get_pixmap(matrix=pymupdf.Matrix(self.scale, self.scale), alpha=False,
|
||||
colorspace=pymupdf.csRGB)
|
||||
rendered.append(Image.frombytes("RGB", (pix.width, pix.height), pix.samples))
|
||||
def __init__(self, parent, path: Path | Image.Image, on_accept, initial=None):
|
||||
# Body edits pass a raster without headers or supplementary pages.
|
||||
width = min(850, parent.winfo_screenwidth() - 100)
|
||||
if isinstance(path, Image.Image):
|
||||
self.heights = [path.height]
|
||||
self.scale = min(1.5, width / path.width)
|
||||
rendered = [path.resize((round(path.width * self.scale),
|
||||
round(path.height * self.scale)), Image.Resampling.LANCZOS)]
|
||||
title = "Corps de la réponse annotée"
|
||||
else:
|
||||
with pymupdf.open(path) as document:
|
||||
if not len(document):
|
||||
raise ValueError("Le PDF est vide.")
|
||||
self.heights = [page.rect.height for page in document]
|
||||
self.scale = min(1.5, width / max(page.rect.width for page in document))
|
||||
rendered = []
|
||||
for page in document:
|
||||
pix = page.get_pixmap(matrix=pymupdf.Matrix(self.scale, self.scale), alpha=False,
|
||||
colorspace=pymupdf.csRGB)
|
||||
rendered.append(Image.frombytes("RGB", (pix.width, pix.height), pix.samples))
|
||||
title = path.name
|
||||
super().__init__(parent)
|
||||
self.title(f"Cut — {path.name}")
|
||||
self.title(f"Cut — {title}")
|
||||
self.geometry(f"{width + 45}x{min(850, parent.winfo_screenheight() - 100)}")
|
||||
self.transient(parent.winfo_toplevel())
|
||||
self.on_accept = on_accept
|
||||
@@ -125,7 +133,7 @@ class CutHelper(tk.Toplevel):
|
||||
operator = cut_operator(self.percent, self.keep.get(), self.mode.get())
|
||||
rounded = float(operator.split("{")[1].split("}")[0])
|
||||
if not 0 < rounded < 100:
|
||||
messagebox.showerror("Coupe invalide", "Chaque partie doit contenir une portion du PDF.", parent=self)
|
||||
messagebox.showerror("Coupe invalide", "Chaque partie doit contenir une portion de la réponse.", parent=self)
|
||||
return
|
||||
self.destroy()
|
||||
self.on_accept(operator)
|
||||
@@ -37,9 +37,10 @@ Enregistrez dans l’éditeur, puis cliquez sur Recharger et enfin sur Exécuter
|
||||
|
||||
|
||||
class ManualResolutionPanel(ttk.Frame):
|
||||
def __init__(self, parent, get_evaluation):
|
||||
def __init__(self, parent, get_evaluation, *, help_text=HELP, show_provenance=True):
|
||||
super().__init__(parent)
|
||||
self.get_evaluation = get_evaluation
|
||||
self.show_provenance = show_provenance
|
||||
self.pdf_buttons = []
|
||||
self.cut_buttons = []
|
||||
self.copy_buttons = []
|
||||
@@ -67,7 +68,7 @@ class ManualResolutionPanel(ttk.Frame):
|
||||
self.text.configure(yscrollcommand=scroll.set)
|
||||
scroll.pack(side="right", fill="y")
|
||||
self.text.pack(fill="both", expand=True)
|
||||
ttk.Label(self, text=HELP, wraplength=650, justify="left").pack(fill="x", pady=8)
|
||||
ttk.Label(self, text=help_text, wraplength=650, justify="left").pack(fill="x", pady=8)
|
||||
self.reload()
|
||||
|
||||
def manual_path(self) -> Path | None:
|
||||
@@ -123,7 +124,7 @@ class ManualResolutionPanel(ttk.Frame):
|
||||
raise FileNotFoundError("Chargez une évaluation.")
|
||||
content = path.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeError) as exc:
|
||||
self.status.configure(text=f"manual_resolutions.txt indisponible : {exc}")
|
||||
self.status.configure(text=f"{path.name if path else 'Fichier'} indisponible : {exc}")
|
||||
else:
|
||||
self.editor_button.configure(state="normal")
|
||||
malformed = []
|
||||
@@ -142,7 +143,7 @@ class ManualResolutionPanel(ttk.Frame):
|
||||
key = (instruction.copy_id, instruction.old_label)
|
||||
if key not in provenance:
|
||||
try:
|
||||
provenance[key] = populated_labels(path.parent, *key)
|
||||
provenance[key] = populated_labels(path.parent, *key) if self.show_provenance else []
|
||||
except (OSError, ValueError) as exc:
|
||||
provenance[key] = []
|
||||
provenance_error = f" — Provenance indisponible : {exc}"
|
||||
|
||||
@@ -668,6 +668,23 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
||||
),
|
||||
) + return_answer_arguments,
|
||||
),
|
||||
StepDefinition(
|
||||
"modify_annotations",
|
||||
"Finalisation",
|
||||
"Modifier les réponses annotées",
|
||||
"Coupez ou transférez les corps de réponses après lecture des annotations. "
|
||||
"Les en-têtes, les scores et les documents complémentaires restent associés à chaque label.",
|
||||
(python("default", "Modifications", "modify-annotations"),),
|
||||
arguments=(
|
||||
arg_target("le dossier de l’évaluation"),
|
||||
ArgumentSpec("annotation_dir", "Dossier d’annotations", "choice", "--annotation-dir",
|
||||
default="BGnot", choices=("BGnot", "Bnot"),
|
||||
help="Choisissez le mode utilisé pour lire les annotations manuscrites ; les coupes portent uniquement sur les corps des réponses."),
|
||||
),
|
||||
optional=True,
|
||||
requires=("labels",),
|
||||
artifacts=("annotation_modifications.txt",),
|
||||
),
|
||||
StepDefinition(
|
||||
"giving_names",
|
||||
"Finalisation",
|
||||
|
||||
Reference in new issue
Block a user