Miscs improvements (Interro02)
This commit is contained in:
@@ -7,11 +7,17 @@ from pathlib import Path
|
||||
import pandas as pd
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from copienator.configuration import FINAL_SCORE_FONT_PATH, FINAL_SCORE_ODS_PATH, FINAL_SCORE_OUTPUT_DIR
|
||||
from copienator.configuration import (
|
||||
FINAL_SCORE_FONT_PATH,
|
||||
FINAL_SCORE_HISTOGRAM_PATH,
|
||||
FINAL_SCORE_ODS_PATH,
|
||||
FINAL_SCORE_OUTPUT_DIR,
|
||||
)
|
||||
|
||||
# Configuration constants
|
||||
ODS_PATH = Path(FINAL_SCORE_ODS_PATH).expanduser()
|
||||
OUTPUT_DIR = Path(FINAL_SCORE_OUTPUT_DIR).expanduser()
|
||||
HISTOGRAM_PATH = Path(FINAL_SCORE_HISTOGRAM_PATH).expanduser()
|
||||
|
||||
|
||||
def score_font(size):
|
||||
@@ -33,6 +39,35 @@ def get_rounded_score(score):
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
|
||||
def copy_return_artifacts(source_dir: Path, destination_dir: Path) -> None:
|
||||
"""Copy the metadata and optional individual answers for one student."""
|
||||
for filename in ("score.json", "info.json"):
|
||||
source = source_dir / filename
|
||||
if source.is_file():
|
||||
shutil.copy2(source, destination_dir / filename)
|
||||
else:
|
||||
print(f"Warning: Missing '{source}'.")
|
||||
|
||||
answers_source = source_dir / "answers"
|
||||
answers_destination = destination_dir / "answers"
|
||||
if answers_destination.is_symlink():
|
||||
answers_destination.unlink()
|
||||
elif answers_destination.is_dir():
|
||||
shutil.rmtree(answers_destination)
|
||||
if answers_source.is_dir():
|
||||
shutil.copytree(answers_source, answers_destination)
|
||||
|
||||
|
||||
def copy_histogram(output_dir: Path) -> None:
|
||||
"""Copy the score histogram beside the per-student output folders."""
|
||||
if not HISTOGRAM_PATH.is_file():
|
||||
print(f"Warning: Missing histogram '{HISTOGRAM_PATH}'.")
|
||||
return
|
||||
destination = output_dir / "histogramme.pdf"
|
||||
shutil.copy2(HISTOGRAM_PATH, destination)
|
||||
print(f"Copied histogram: {destination}")
|
||||
|
||||
def process_images(base_dir, output_dir):
|
||||
# 1. Load Data
|
||||
try:
|
||||
@@ -56,60 +91,64 @@ def process_images(base_dir, output_dir):
|
||||
print(f"Error: Directory '{search_path}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
for img_path in sorted(search_path.glob("*/*.jpg")):
|
||||
student_name = img_path.stem # Filename without extension
|
||||
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"))
|
||||
media_paths = image_paths or pdf_paths
|
||||
if not media_paths:
|
||||
print(f"Error: No JPG or PDF found in '{student_source}'.")
|
||||
continue
|
||||
|
||||
student_name = media_paths[0].stem
|
||||
student_output = output_dir / student_name
|
||||
student_output.mkdir(parents=True, exist_ok=True)
|
||||
# Remove files produced by the former flat output layout when migrating
|
||||
# an existing export directory.
|
||||
for suffix in (".jpg", ".pdf"):
|
||||
legacy_output = output_dir / f"{student_name}{suffix}"
|
||||
if legacy_output.is_file() or legacy_output.is_symlink():
|
||||
legacy_output.unlink()
|
||||
copy_return_artifacts(student_source, student_output)
|
||||
|
||||
# 4. Find Score
|
||||
if student_name not in score_db:
|
||||
print(f"Error: Student '{student_name}' not found in ODS file.")
|
||||
continue
|
||||
else:
|
||||
raw_score = score_db[student_name]
|
||||
score = get_rounded_score(raw_score)
|
||||
|
||||
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}'.")
|
||||
else:
|
||||
# 5. Process Images
|
||||
for img_path in image_paths:
|
||||
try:
|
||||
with Image.open(img_path) as img:
|
||||
img = img.convert("RGB")
|
||||
draw = ImageDraw.Draw(img)
|
||||
width, _height = img.size
|
||||
|
||||
if score is None:
|
||||
print(f"Error: Invalid score '{raw_score}' for '{student_name}'.")
|
||||
continue
|
||||
font_size = int(width * 0.08)
|
||||
font = score_font(font_size)
|
||||
text = str(score)
|
||||
|
||||
# 5. Process Image
|
||||
try:
|
||||
with Image.open(img_path) as img:
|
||||
img = img.convert("RGB")
|
||||
draw = ImageDraw.Draw(img)
|
||||
width, height = img.size
|
||||
bbox = draw.textbbox((0, 0), text, font=font)
|
||||
text_w = bbox[2] - bbox[0]
|
||||
|
||||
# Dynamic font size (15% of image height)
|
||||
font_size = int(width * 0.08)
|
||||
# 30px padding, top right.
|
||||
x = width - text_w - 30
|
||||
y = 30
|
||||
draw.text((x, y), text, fill=(255, 0, 0), font=font)
|
||||
|
||||
font = score_font(font_size)
|
||||
img.save(student_output / img_path.name)
|
||||
print(f"Processed: {student_name} -> {score}")
|
||||
except Exception as e:
|
||||
print(f"Error processing image for '{student_name}': {e}")
|
||||
|
||||
text = str(score)
|
||||
for pdf_path in pdf_paths:
|
||||
shutil.copy2(pdf_path, student_output / pdf_path.name)
|
||||
|
||||
# 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}")
|
||||
|
||||
for pdf_path in sorted(search_path.glob("*/*.pdf")):
|
||||
student_name = pdf_path.stem # Filename without extension
|
||||
save_path = output_dir / f"{student_name}.pdf"
|
||||
|
||||
shutil.copy(str(pdf_path), str(save_path))
|
||||
copy_histogram(output_dir)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
|
||||
Reference in New Issue
Block a user