Improve web finalisation navigation, caching and Anot support
This commit is contained in:
1 parent
5de70dbc0d
commit
b069805988
21 files changed
+339
-58
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import argparse
|
||||
import math
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from decimal import Decimal, InvalidOperation, ROUND_CEILING
|
||||
@@ -37,7 +38,7 @@ def get_rounded_score(score):
|
||||
"""Round score to one decimal place below (floor)."""
|
||||
try:
|
||||
val = float(score)
|
||||
return math.floor(val * 10) / 10
|
||||
return math.floor(val * 10) / 10 if math.isfinite(val) else None
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
@@ -130,6 +131,7 @@ def process_images(base_dir, output_dir, *, copy_merged_histogramme=False):
|
||||
print(f"Error: Directory '{search_path}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
exported_marks = {}
|
||||
for student_source in sorted(path for path in search_path.iterdir() if path.is_dir()):
|
||||
image_paths = sorted(student_source.glob("*.jpg"))
|
||||
pdf_paths = sorted(student_source.glob("*.pdf"))
|
||||
@@ -159,6 +161,7 @@ def process_images(base_dir, output_dir, *, copy_merged_histogramme=False):
|
||||
if score is None:
|
||||
print(f"Error: Invalid score '{raw_score}' for '{student_name}'.")
|
||||
else:
|
||||
score_published = not image_paths
|
||||
# 5. Process Images
|
||||
for img_path in image_paths:
|
||||
try:
|
||||
@@ -180,13 +183,20 @@ def process_images(base_dir, output_dir, *, copy_merged_histogramme=False):
|
||||
draw.text((x, y), text, fill=(255, 0, 0), font=font)
|
||||
|
||||
img.save(student_output / img_path.name)
|
||||
score_published = True
|
||||
print(f"Processed: {student_name} -> {score}")
|
||||
except Exception as e:
|
||||
print(f"Error processing image for '{student_name}': {e}")
|
||||
|
||||
match = re.search(r'\((\d+)\)$', student_source.name)
|
||||
if match and score_published:
|
||||
exported_marks[str(int(match[1]))] = {'name': student_name, 'score': str(score)}
|
||||
|
||||
for pdf_path in pdf_paths:
|
||||
shutil.copy2(pdf_path, student_output / pdf_path.name)
|
||||
|
||||
from copienator.final_scores import FINAL_SCORES_FILE
|
||||
atomic_write_json(base_dir / FINAL_SCORES_FILE, {'version': 1, 'copies': exported_marks})
|
||||
write_score_statistics(search_path, output_dir)
|
||||
copy_histogram(output_dir, copy_merged_histogramme=copy_merged_histogramme)
|
||||
|
||||
|
||||
@@ -516,6 +516,7 @@ def process_student(student_id, labels_data, root_dir, all_labels, overwrite):
|
||||
d_notes = dict.fromkeys(all_labels, "")
|
||||
label_images = []
|
||||
answer_labels = []
|
||||
reviewed_layouts = {}
|
||||
sorted_labels = sorted(labels_data.items(), key=lambda item: natural_key(item[0]))
|
||||
|
||||
for label, content in sorted_labels:
|
||||
@@ -534,7 +535,7 @@ def process_student(student_id, labels_data, root_dir, all_labels, overwrite):
|
||||
result = content.get('result', {})
|
||||
coordinates = content.get('coordinates', (0, 0))
|
||||
d_notes[label] = str(result.get('score', 0))
|
||||
final_img, _ = compose_label_image(
|
||||
final_img, header_height = compose_label_image(
|
||||
base_img,
|
||||
label,
|
||||
result,
|
||||
@@ -543,6 +544,8 @@ def process_student(student_id, labels_data, root_dir, all_labels, overwrite):
|
||||
render_fn=render_real_latex_text,
|
||||
source_pdf=pdf_full_path,
|
||||
)
|
||||
reviewed_layouts[label] = {'img': None, 'old_header_h': header_height,
|
||||
'layout': final_img.info.get('annotation_layout', layout_for(pdf_full_path))}
|
||||
final_img.save(staging / f"{label}.jpg")
|
||||
if result.get('error', "") != "empty-answer":
|
||||
label_images.append(final_img)
|
||||
@@ -561,7 +564,13 @@ def process_student(student_id, labels_data, root_dir, all_labels, overwrite):
|
||||
canvas.paste(image, (0, current_y))
|
||||
current_y += image.height
|
||||
canvas.save(staging / "Concat.jpg")
|
||||
if not problems:
|
||||
from copienator.review_snapshot import stage
|
||||
stage(output_dir, staging, labels_data, reviewed_layouts, engine='grouped')
|
||||
# An all-empty copy still has valid individual return images.
|
||||
if not problems:
|
||||
from copienator.reviewed_cache import ReviewedReadCache
|
||||
ReviewedReadCache(EvaluationWorkspace(Path(root_dir)), "Anot", [output_dir]).remember([student_id])
|
||||
return "partial" if problems else "success"
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from copienator import evaluation_parser
|
||||
|
||||
def main(argv=None):
|
||||
parser = evaluation_parser('Open the local finalisation workspace')
|
||||
parser.add_argument('--annotation-dir', choices=('BGnot', 'Bnot'), default='BGnot')
|
||||
parser.add_argument('--annotation-dir', choices=('BGnot', 'Bnot', 'Anot'), default='BGnot')
|
||||
parser.add_argument('--no-browser', action='store_true')
|
||||
args = parser.parse_args(argv)
|
||||
from copienator.finalisation_server import WebSession
|
||||
|
||||
@@ -455,7 +455,7 @@ def apply_actions_and_regenerate_grouped(
|
||||
[(label, dirty_images[label]) for label in filtered_labels],
|
||||
all_labels, staging / "Concat_F.pdf", general=general,
|
||||
)
|
||||
if annotation_dir in {"Bnot", "BGnot"}:
|
||||
if annotation_dir in {"Bnot", "BGnot", "Anot"}:
|
||||
review_snapshot.stage(output_dir, staging, reviewed_data, label_notes,
|
||||
engine="grouped", selected=selected_labels,
|
||||
force_filtered=(set(filtered_labels) - selected_labels) if selected_labels else (force_filtered or ()))
|
||||
|
||||
@@ -8,6 +8,26 @@ from copienator.commands.reading_grouped_annotations import apply_actions_and_re
|
||||
def run(workspace: EvaluationWorkspace, mode: str, *, incremental: bool = False):
|
||||
directories = sorted((p for p in (workspace.root / mode).glob('Copie*')
|
||||
if p.is_dir() and p.name[5:].isdigit() and (p / 'score.json').is_file()))
|
||||
# Legacy Anot outputs predate saved rendering inputs; create them without
|
||||
# replacing any existing score or annotated image.
|
||||
missing = [d for d in directories if not (d / FILENAME).is_file()]
|
||||
if mode == 'Anot' and missing:
|
||||
from copienator.annotation_data import load_annotation_data
|
||||
from copienator.annotation_layout import layout_for
|
||||
from copienator.filesystem import staged_files
|
||||
from copienator.review_snapshot import stage
|
||||
loaded = load_annotation_data(workspace)
|
||||
if loaded.warnings:
|
||||
raise ValueError("Relancez la génération Anot : " + "; ".join(loaded.warnings))
|
||||
for directory in missing:
|
||||
cid = directory.name[5:]
|
||||
if cid not in loaded.data:
|
||||
raise ValueError(f"Données Anot absentes pour {directory.name}.")
|
||||
data = loaded.data[cid]
|
||||
notes = {label: {'img': None, 'old_header_h': 0, 'layout': layout_for(content['pdf_path'])}
|
||||
for label, content in data.items()}
|
||||
with staged_files(directory) as staging:
|
||||
stage(directory, staging, data, notes, engine='grouped')
|
||||
from copienator.reviewed_cache import ReviewedReadCache
|
||||
cache = ReviewedReadCache(workspace, mode, directories) if incremental else None
|
||||
if cache:
|
||||
@@ -47,6 +67,6 @@ def run(workspace: EvaluationWorkspace, mode: str, *, incremental: bool = False)
|
||||
|
||||
def main(argv=None):
|
||||
parser = evaluation_parser('Regenerate from saved reviewed inputs without rescanning handwriting')
|
||||
parser.add_argument('--annotation-dir', choices=('Bnot', 'BGnot'), required=True)
|
||||
parser.add_argument('--annotation-dir', choices=('Bnot', 'BGnot', 'Anot'), required=True)
|
||||
parser.add_argument("--incremental", action="store_true", help="Skip unchanged copies")
|
||||
return execute(parser, argv, lambda args: run(workspace_from_args(args), args.annotation_dir, incremental=args.incremental))
|
||||
Reference in new issue
Block a user