From 67d6355ed220b68a74925fc44134d6f3a459a459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Miquel?= Date: Fri, 27 Feb 2026 12:31:54 +0100 Subject: [PATCH] add_final_score.py --- add_final_score.py | 104 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 add_final_score.py diff --git a/add_final_score.py b/add_final_score.py new file mode 100644 index 0000000..a9f0831 --- /dev/null +++ b/add_final_score.py @@ -0,0 +1,104 @@ +import argparse +import math +import sys +import os +from pathlib import Path +import pandas as pd +from PIL import Image, ImageDraw, ImageFont + +# Configuration constants +ODS_PATH = "/home/sebastien/Rust/gestion_classe/Staging/simple_eval.ods" +OUTPUT_DIR = Path("/home/sebastien/Rust/Server/assets/static/copies") +FONT_PATH = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" # Standard Linux font path + +def get_rounded_score(score): + """Round score to one decimal place below (floor).""" + try: + val = float(score) + return math.floor(val * 10) / 10 + except (ValueError, TypeError): + return None + +def process_images(base_dir): + # 1. Load Data + try: + # header=None assumes the file starts directly with data. + # If row 0 is a header, change to header=0 + df = pd.read_excel(ODS_PATH, engine="odf", header=None) + # Create a lookup dictionary: {Name: Score} + score_db = dict(zip(df[0], df[1])) + except Exception as e: + print(f"CRITICAL ERROR: Could not read ODS file.\n{e}") + sys.exit(1) + + # 2. Prepare Output Directory + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + # 3. Iterate Files + # Structure: Dir/A Rendre/{name}/{name}.jpg + search_path = base_dir / "A Rendre" + + if not search_path.exists(): + print(f"Error: Directory '{search_path}' not found.") + sys.exit(1) + + for img_path in search_path.glob("*/*.jpg"): + student_name = img_path.stem # Filename without extension + + # 4. Find Score + if student_name not in score_db: + print(f"Error: Student '{student_name}' not found in ODS file.") + continue + + raw_score = score_db[student_name] + score = get_rounded_score(raw_score) + + if score is None: + print(f"Error: Invalid score '{raw_score}' for '{student_name}'.") + continue + + # 5. Process Image + try: + with Image.open(img_path) as img: + img = img.convert("RGB") + draw = ImageDraw.Draw(img) + width, height = img.size + + # Dynamic font size (15% of image height) + font_size = int(width * 0.08) + + try: + font = ImageFont.truetype(FONT_PATH, font_size) + except IOError: + # Fallback if specific font not found + font = ImageFont.load_default() + print(f"Warning: System font not found, using default for {student_name}") + + text = str(score) + + # Calculate text size and position (Top Right) + bbox = draw.textbbox((0, 0), text, font=font) + text_w = bbox[2] - bbox[0] + text_h = bbox[3] - bbox[1] + + # 30px padding + x = width - text_w - 30 + y = 30 + + # Draw Text (Red) + draw.text((x, y), text, fill=(255, 0, 0), font=font) + + # Save + save_path = OUTPUT_DIR / f"{student_name}.jpg" + img.save(save_path) + print(f"Processed: {student_name} -> {score}") + + except Exception as e: + print(f"Error processing image for '{student_name}': {e}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Stamp scores on exam copies.") + parser.add_argument("dir", type=Path, help="Root directory containing 'A Rendre' folder") + + args = parser.parse_args() + process_images(args.dir)