Make reading grouped annotations faster.

master
Sébastien Miquel 2026-03-10 15:04:50 +01:00
parent a658cb72e0
commit d288daecd1
2 changed files with 27 additions and 23 deletions

View File

@ -42,7 +42,7 @@ def process_images(base_dir):
print(f"Error: Directory '{search_path}' not found.")
sys.exit(1)
for img_path in search_path.glob("*/*.jpg"):
for img_path in sorted(search_path.glob("*/*.jpg")):
student_name = img_path.stem # Filename without extension
# 4. Find Score

View File

@ -2,6 +2,7 @@ import sys
import os
import json
import collections
import concurrent.futures
from pathlib import Path
from PIL import Image
@ -14,7 +15,9 @@ def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, la
Modifies data based on actions, pastes label-specific note crops,
regenerates label images for consistency, saves dirty ones,
and generates Concat.jpg in the BGnot/Copie{id} directory.
Returns a string of accumulated log messages.
"""
logs = [f"\nProcessing compilation for: Copie{student_id}"]
output_dir = os.path.join(root_dir, "BGnot", f"Copie{student_id}")
os.makedirs(output_dir, exist_ok=True)
@ -44,23 +47,23 @@ def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, la
if act['type'] == 'score':
result['score'] = act['value']
dirty_labels.add(label)
print(f" > Updated score for {label} to {act['value']}")
logs.append(f" > Updated score for {label} to {act['value']}")
elif act['type'] == 'del_global':
if act['index'] < len(global_fb):
global_fb[act['index']]["to_delete"] = True
dirty_labels.add(label)
print(f" > Deleted global feedback in {label}")
logs.append(f" > Deleted global feedback in {label}")
elif act['type'] in ('del_local', 'del_local_rect'):
if act['index'] < len(local_fb):
target = local_fb[act['index']]
if act['type'] == 'del_local':
target["to_delete"] = True
print(f" > Deleted local feedback in {label}")
logs.append(f" > Deleted local feedback in {label}")
else:
target["norectangle"] = True
print(f" > Deleted rect in {label}")
logs.append(f" > Deleted rect in {label}")
dirty_labels.add(label)
# --- 2. Process Images (Regenerate & Concatenate) ---
@ -113,7 +116,7 @@ def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, la
if (label in dirty_labels) or has_notes:
save_path = os.path.join(output_dir, f"{label}.jpg")
final_img.save(save_path)
print(f" Saved dirty image: {label}.jpg")
logs.append(f" Saved dirty image: {label}.jpg")
concat_list.append(final_img)
@ -126,11 +129,10 @@ def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, la
if not perfect_no_comment:
concat_list_F.append(final_img)
# --- 3. Save Final Outputs ---
with open(score_path, "w") as f:
json.dump(d_notes, f, indent=4)
print(f" Saved {score_path}")
logs.append(f" Saved {score_path}")
if concat_list:
max_w = max(i.width for i in concat_list)
@ -143,7 +145,8 @@ def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, la
y += img.height
full_img.save(os.path.join(output_dir, "Concat.jpg"))
print(f" Saved regenerated Concat.jpg")
logs.append(f" Saved regenerated Concat.jpg")
if concat_list_F:
max_w = max(i.width for i in concat_list_F)
total_h = sum(i.height for i in concat_list_F)
@ -155,7 +158,9 @@ def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, la
y += img.height
full_img.save(os.path.join(output_dir, "Concat_F.jpg"))
print(f" Saved regenerated Concat_F.jpg")
logs.append(f" Saved regenerated Concat_F.jpg")
return "\n".join(logs)
if __name__ == "__main__":
@ -223,20 +228,10 @@ if __name__ == "__main__":
'old_header_h': img_info.get("header_height", 0)
}
# --- 2. Dispatch data back to students and regenerate ---
# affected_students = set(actions_by_student.keys()).union(set(notes_by_student.keys()))
# if not affected_students:
# print("\nNo changes detected in any grouped annotations.")
# sys.exit(0)
# for sid in sorted(affected_students, key=natural_key):
for sid in sorted(original_data.keys(), key=natural_key):
def process_student(sid):
if sid not in original_data:
continue
print(f"\nProcessing compilation for: Copie{sid}")
apply_actions_and_regenerate_grouped(
return ""
return apply_actions_and_regenerate_grouped(
root_dir,
original_data,
sid,
@ -244,3 +239,12 @@ if __name__ == "__main__":
notes_by_student[sid],
all_labels
)
# --- 2. Process each student concurrently using 4 threads ---
sids = sorted(original_data.keys(), key=natural_key)
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
futures = {executor.submit(process_student, sid): sid for sid in sids}
for future in concurrent.futures.as_completed(futures):
output = future.result()
if output:
print(output)