Working state, mostly…
This commit is contained in:
+53
-32
@@ -8,6 +8,8 @@ Image.MAX_IMAGE_PIXELS = None
|
||||
from pdf2image import convert_from_path
|
||||
import annotating # Reuse rendering logic
|
||||
|
||||
DPI = 100
|
||||
|
||||
def detect_checks_and_notes(output_dir):
|
||||
"""
|
||||
Returns:
|
||||
@@ -33,11 +35,11 @@ def detect_checks_and_notes(output_dir):
|
||||
# Warning: If the PDF is huge, pdf2image might split pages or OOM.
|
||||
# Assuming user didn't change page dimensions/order.
|
||||
try:
|
||||
user_pages = convert_from_path(pdf_path)
|
||||
user_pages = convert_from_path(pdf_path, dpi=DPI)
|
||||
except Exception as e:
|
||||
print(f"Error reading PDF: {e}")
|
||||
return [], None
|
||||
print("Debug : user_pages", len(user_pages))
|
||||
# print("Debug : user_pages", len(user_pages))
|
||||
# Concatenate PDF pages back to one image if user saved as multiple pages
|
||||
# (Xournal++ might preserve the long format or split it)
|
||||
total_h = sum(p.height for p in user_pages)
|
||||
@@ -133,6 +135,12 @@ def detect_checks_and_notes(output_dir):
|
||||
|
||||
from PIL import ImageDraw
|
||||
|
||||
import re
|
||||
def natural_key(text):
|
||||
return [int(c) if c.isdigit() else c.lower() for c in re.split(r'(\d+)', str(text))]
|
||||
|
||||
from annotating import MARGIN_LEFT, ANNOT_WIDTH
|
||||
|
||||
def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_layer):
|
||||
"""
|
||||
Modifies data based on actions, calls annotating.process_correction logic,
|
||||
@@ -148,10 +156,12 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||
actions_by_label = {}
|
||||
for a in actions:
|
||||
l = a['label']
|
||||
if l not in actions_by_label: actions_by_label[l] = []
|
||||
if l not in actions_by_label:
|
||||
actions_by_label[l] = []
|
||||
actions_by_label[l].append(a)
|
||||
|
||||
for label, acts in actions_by_label.items():
|
||||
for label, acts in sorted(actions_by_label.items(), key=lambda x: natural_key(x[0])):
|
||||
# print(label)
|
||||
if label not in labels: continue
|
||||
|
||||
content = labels[label]
|
||||
@@ -162,12 +172,13 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||
global_fb_indices = [i for i, f in enumerate(feedbacks) if not f.get('box_2d')]
|
||||
local_fb_indices = [i for i, f in enumerate(feedbacks) if f.get('box_2d')]
|
||||
# Sort local by Y to match generation order in annotating.py
|
||||
local_fb_sorted_map = sorted(local_fb_indices, key=lambda i: feedbacks[i]['box_2d'][0])
|
||||
local_fb_sorted_map = sorted(local_fb_indices,
|
||||
key=lambda i: feedbacks[i]['box_2d'][0])
|
||||
|
||||
items_to_remove = set()
|
||||
|
||||
for act in acts:
|
||||
if act['type'] == 'set_score':
|
||||
if act['type'] == 'score':
|
||||
result['score'] = act['value']
|
||||
print(f" > Updated score for {label} to {act['value']}")
|
||||
|
||||
@@ -176,57 +187,53 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||
# We need to find the actual index in the main list
|
||||
if act['index'] < len(global_fb_indices):
|
||||
real_idx = global_fb_indices[act['index']]
|
||||
items_to_remove.add(real_idx)
|
||||
feedbacks[real_idx]["to_delete"] = None
|
||||
print(f" > Deleted global feedback in {label}")
|
||||
|
||||
elif act['type'] == 'del_local':
|
||||
# act['index'] is index in sorted local list
|
||||
if act['index'] < len(local_fb_sorted_map):
|
||||
real_idx = local_fb_sorted_map[act['index']]
|
||||
items_to_remove.add(real_idx)
|
||||
feedbacks[real_idx]["to_delete"] = None
|
||||
print(f" > Deleted local feedback in {label}")
|
||||
elif act['type'] == 'del_local_rect':
|
||||
# act['index'] is index in sorted local list
|
||||
if act['index'] < len(local_fb_sorted_map):
|
||||
real_idx = local_fb_sorted_map[act['index']]
|
||||
feedbacks[real_idx]["norectangle"] = None
|
||||
print(f" > Deleted rect of local feedback in {label}")
|
||||
|
||||
|
||||
# Remove feedbacks (in reverse to preserve indices)
|
||||
for idx in sorted(list(items_to_remove), reverse=True):
|
||||
del feedbacks[idx]
|
||||
# for idx in sorted(list(items_to_remove), reverse=True):
|
||||
# del feedbacks[idx]
|
||||
|
||||
# 2. Regenerate Clean Image
|
||||
# We use a temporary modified dictionary
|
||||
temp_data = {student_id: labels}
|
||||
|
||||
# Run the original process (but we need to intercept it to not save, or just let it save)
|
||||
# annotating.process_correction saves to "Anot_CopieID".
|
||||
# We want "Bnot_CopieID" (updated).
|
||||
|
||||
# Hijack the output dir in logic or copy code?
|
||||
# Easiest: Let's create a temporary helper or modify annotating logic slightly?
|
||||
# The prompt implies we use `annotating.py` logic.
|
||||
# Let's call `annotating.process_correction` but point it to a temp root or modify path?
|
||||
# No, `process_correction` takes `root_dir` and writes to `Anot_...`.
|
||||
# Let's just implement the rendering loop here to be safe and clean,
|
||||
# overlaying the notes at the end.
|
||||
|
||||
output_dir = os.path.join(root_dir, "Bnot", f"Copie{student_id}")
|
||||
# Don't delete output_dir, we need it.
|
||||
|
||||
# ... (Reuse rendering logic from annotating.py exactly) ...
|
||||
# See below for condensed integration
|
||||
|
||||
final_concats = []
|
||||
|
||||
for label, content in labels.items():
|
||||
for label, content in sorted(labels.items(), key=lambda x: natural_key(x[0])):
|
||||
# ... [PDF to Image Conversion] ...
|
||||
copie_folder = f"Copie{student_id}"
|
||||
pdf_path = os.path.join(root_dir, copie_folder, f"{label}.pdf")
|
||||
if not os.path.exists(pdf_path): continue
|
||||
|
||||
pages = annotating.convert_from_path(pdf_path)
|
||||
base_img = Image.new("RGBA", (max(p.width for p in pages), sum(p.height for p in pages)), "white")
|
||||
y=0
|
||||
for p in pages: base_img.paste(p.convert("RGBA"), (0,y)); y+=p.height
|
||||
(base_img, total_h, max_w) = annotating.make_base_image(pdf_path)
|
||||
|
||||
# ... [Draw Header/Margin (Clean)] ...
|
||||
margin_left = 200
|
||||
margin_left = MARGIN_LEFT
|
||||
result = content['result']
|
||||
coordinates = content.get('coordinates', (0,0))
|
||||
hmin = coordinates[0]
|
||||
@@ -234,7 +241,7 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||
score_text = f"{label} ; Note : {result.get('score', 0)}"
|
||||
if result.get('error') and result.get('error') != "null": score_text += f" | Error: {result.get('error')}"
|
||||
|
||||
header_imgs = [annotating.render_latex_text(score_text, base_img.width, fontsize=18)]
|
||||
header_imgs = [(annotating.render_latex_text(score_text, base_img.width, fontsize=18), True)]
|
||||
|
||||
feedbacks = result.get('feedback', [])
|
||||
# Separate again (now cleaned)
|
||||
@@ -242,13 +249,21 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||
local_fb = [f for f in feedbacks if f.get('box_2d')]
|
||||
local_fb.sort(key=lambda x: x['box_2d'][0])
|
||||
|
||||
for fb in global_fb: header_imgs.append(annotating.render_latex_text(fb['text'], base_img.width))
|
||||
for fb in global_fb:
|
||||
render = annotating.render_latex_text(fb['text'], base_img.width)
|
||||
header_imgs.append((render, "to_delete" not in fb))
|
||||
|
||||
total_h = base_img.height + sum(i.height for i in header_imgs)
|
||||
total_h = base_img.height + sum(i.height for (i,_) in header_imgs)
|
||||
label_img = Image.new("RGB", (base_img.width + margin_left, total_h), "white")
|
||||
|
||||
cy = 0
|
||||
for i in header_imgs: label_img.paste(i, (0, cy)); cy+=i.height
|
||||
for (i, keep) in header_imgs:
|
||||
if keep:
|
||||
label_img.paste(i, (0, cy))
|
||||
else:
|
||||
blank = Image.new("RGB", (i.width, i.height), "white")
|
||||
label_img.paste(blank, (0, cy))
|
||||
cy+=i.height
|
||||
img_offset_y = cy
|
||||
label_img.paste(base_img, (margin_left, img_offset_y))
|
||||
|
||||
@@ -256,14 +271,19 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||
last_bot = 0
|
||||
for fb in local_fb:
|
||||
box = fb['box_2d']
|
||||
|
||||
ymin, xmin, ymax, xmax = box
|
||||
t_ymin = (ymin - hmin) + img_offset_y
|
||||
t_ymax = (ymax - hmin) + img_offset_y
|
||||
draw.rectangle([xmin+margin_left, t_ymin, xmax+margin_left, t_ymax], outline="red", width=3)
|
||||
if "norectangle" not in fb:
|
||||
draw.rectangle([xmin+margin_left, t_ymin, xmax+margin_left, t_ymax],
|
||||
outline="red", width=3)
|
||||
|
||||
txt = annotating.render_latex_text(fb['text'], 500, (255,200,200,180), max_lines=3)
|
||||
txt = annotating.render_latex_text(fb['text'], ANNOT_WIDTH,
|
||||
(255,200,200,180), max_lines=3)
|
||||
py = max((t_ymin+t_ymax)/2 - txt.height/2, img_offset_y)
|
||||
if py < last_bot: py = last_bot + 5
|
||||
if py < last_bot:
|
||||
py = last_bot + 5
|
||||
|
||||
if py + txt.height + 20 > label_img.height:
|
||||
new_l = Image.new("RGB", (label_img.width, int(py+txt.height+20)), "white")
|
||||
@@ -271,7 +291,8 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||
label_img = new_l
|
||||
draw = ImageDraw.Draw(label_img, "RGBA")
|
||||
|
||||
label_img.paste(txt, (10, int(py)), mask=txt)
|
||||
if not "to_delete" in fb:
|
||||
label_img.paste(txt, (10, int(py)), mask=txt)
|
||||
last_bot = py + txt.height
|
||||
|
||||
final_concats.append(label_img)
|
||||
|
||||
Reference in New Issue
Block a user