Move to real LaTeX rendering
parent
6222ba5dac
commit
2677e41b04
|
|
@ -219,6 +219,71 @@ def render_latex_text(text, width_px, bg_color=(255, 255, 255, 255), max_lines=N
|
||||||
final_img.alpha_composite(img)
|
final_img.alpha_composite(img)
|
||||||
return final_img
|
return final_img
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
import PIL.ImageOps
|
||||||
|
|
||||||
|
|
||||||
|
def render_real_latex_text(text, width_px, bg_color=(255, 255, 255, 255), max_lines=None, fontsize=19):
|
||||||
|
dpi = 100
|
||||||
|
width_in = width_px / dpi
|
||||||
|
line_spacing = int(fontsize * 1.2)
|
||||||
|
|
||||||
|
# Use the 'standalone' class with 'varwidth' to auto-crop height while restricting width
|
||||||
|
latex_template = f"""\\documentclass[varwidth={width_in}in,margin=0.2cm]{{standalone}}
|
||||||
|
\\usepackage[utf8]{{inputenc}}
|
||||||
|
\\usepackage[T1]{{fontenc}}
|
||||||
|
\\usepackage{{lmodern}} % Enables arbitrary font scaling
|
||||||
|
\\usepackage{{amsmath, amssymb}}
|
||||||
|
%\\usepackage{{anyfontsize}} % replaces by lmodern
|
||||||
|
\\begin{{document}}
|
||||||
|
\\fontsize{{{fontsize}}}{{{line_spacing}}}\\selectfont
|
||||||
|
{text}
|
||||||
|
\\end{{document}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
tex_path = os.path.join(temp_dir, 'text.tex')
|
||||||
|
pdf_path = os.path.join(temp_dir, 'text.pdf')
|
||||||
|
|
||||||
|
with open(tex_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(latex_template)
|
||||||
|
|
||||||
|
# Compile to PDF
|
||||||
|
result = subprocess.run(
|
||||||
|
['pdflatex', '-interaction=nonstopmode', 'text.tex'],
|
||||||
|
cwd=temp_dir,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(pdf_path):
|
||||||
|
raise RuntimeError("LaTeX compilation failed. Check your LaTeX syntax.")
|
||||||
|
|
||||||
|
# Convert PDF to grayscale (ignoring pdf2image's broken transparency)
|
||||||
|
images = convert_from_path(pdf_path, dpi=dpi)
|
||||||
|
gray_img = images[0].convert("L")
|
||||||
|
|
||||||
|
# 1. Invert grayscale to create an alpha mask (white bg = 0, black text = 255)
|
||||||
|
alpha_mask = PIL.ImageOps.invert(gray_img)
|
||||||
|
|
||||||
|
# 2. Create a transparent image with black text using the mask
|
||||||
|
text_img = Image.new("RGBA", gray_img.size, (0, 0, 0, 255))
|
||||||
|
text_img.putalpha(alpha_mask)
|
||||||
|
|
||||||
|
# 3. Create the requested background and composite the text over it
|
||||||
|
final_img = Image.new("RGBA", text_img.size, bg_color)
|
||||||
|
final_img.alpha_composite(text_img)
|
||||||
|
|
||||||
|
# (Optional) Truncate image height if max_lines is strictly enforced
|
||||||
|
if max_lines:
|
||||||
|
max_height_px = int((fontsize * 1.2 / 72.0) * dpi * max_lines) # Points to pixels
|
||||||
|
if final_img.height > max_height_px:
|
||||||
|
final_img = final_img.crop((0, 0, final_img.width, max_height_px))
|
||||||
|
|
||||||
|
return final_img
|
||||||
|
|
||||||
import io
|
import io
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
|
|
@ -376,7 +441,7 @@ def compose_label_image(base_img, label, result, hmin,
|
||||||
|
|
||||||
# Render Text
|
# Render Text
|
||||||
txt_img = render_fn(fb['text'], width_px=ANNOT_WIDTH,
|
txt_img = render_fn(fb['text'], width_px=ANNOT_WIDTH,
|
||||||
bg_color=(255, 200, 200, 180), max_lines=3)
|
bg_color=(255, 200, 200, 180), max_lines=None)
|
||||||
|
|
||||||
# Calculate Position
|
# Calculate Position
|
||||||
center_y = (target_ymin + target_ymax) / 2
|
center_y = (target_ymin + target_ymax) / 2
|
||||||
|
|
@ -458,7 +523,8 @@ def process_student(student_id, labels_data, root_dir, all_labels, overwrite):
|
||||||
d_notes[label] = str(score)
|
d_notes[label] = str(score)
|
||||||
|
|
||||||
final_img, _ = compose_label_image(base_img, label, result, coordinates[0],
|
final_img, _ = compose_label_image(base_img, label, result, coordinates[0],
|
||||||
with_empty=True)
|
with_empty=True,
|
||||||
|
render_fn=render_real_latex_text)
|
||||||
# 7. Save Image
|
# 7. Save Image
|
||||||
save_path = os.path.join(output_dir, f"{label}.jpg")
|
save_path = os.path.join(output_dir, f"{label}.jpg")
|
||||||
final_img.save(save_path)
|
final_img.save(save_path)
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,9 @@ def draw_checkbox(draw, x, y, size=BOX_SIZE, label=None, fill="white"):
|
||||||
|
|
||||||
def safe_render_latex(*args, **kwargs):
|
def safe_render_latex(*args, **kwargs):
|
||||||
"""Thread-safe wrapper for latex rendering."""
|
"""Thread-safe wrapper for latex rendering."""
|
||||||
with LATEX_LOCK:
|
# with LATEX_LOCK:
|
||||||
return annotating.render_latex_text(*args, **kwargs)
|
# return annotating.render_latex_text(*args, **kwargs)
|
||||||
|
return annotating.render_real_latex_text(*args, **kwargs)
|
||||||
|
|
||||||
class CheckboxRenderer:
|
class CheckboxRenderer:
|
||||||
def __init__(self, label_name):
|
def __init__(self, label_name):
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||||
|
|
||||||
# --- 2. Process Images (Cut notes, Regenerate, Concatenate) ---
|
# --- 2. Process Images (Cut notes, Regenerate, Concatenate) ---
|
||||||
concat_list = []
|
concat_list = []
|
||||||
|
concat_list_F = []
|
||||||
d_notes = dict.fromkeys(all_labels, "")
|
d_notes = dict.fromkeys(all_labels, "")
|
||||||
|
|
||||||
# Iterate over images defined in bnote.json to maintain order/geometry
|
# Iterate over images defined in bnote.json to maintain order/geometry
|
||||||
|
|
@ -235,7 +236,8 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||||
|
|
||||||
# Update scores dict
|
# Update scores dict
|
||||||
content = labels_data[label]
|
content = labels_data[label]
|
||||||
d_notes[label] = str(content['result'].get('score', 0))
|
result = content['result']
|
||||||
|
d_notes[label] = str(result.get('score', 0))
|
||||||
|
|
||||||
# A. Cut Manual Notes
|
# A. Cut Manual Notes
|
||||||
hmin, hmax = img_info["hmin"], img_info["hmax"]
|
hmin, hmax = img_info["hmin"], img_info["hmax"]
|
||||||
|
|
@ -272,6 +274,14 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||||
|
|
||||||
concat_list.append(final_img)
|
concat_list.append(final_img)
|
||||||
|
|
||||||
|
perfect_no_comment = True
|
||||||
|
if float(d_notes[label]) != 4.0:
|
||||||
|
perfect_no_comment = False
|
||||||
|
if len(result.get('feedback', [])) != 0:
|
||||||
|
perfect_no_comment = False
|
||||||
|
if not perfect_no_comment:
|
||||||
|
concat_list_F.append(final_img)
|
||||||
|
|
||||||
# --- 3. Save Final Outputs ---
|
# --- 3. Save Final Outputs ---
|
||||||
with open(score_path, "w") as f:
|
with open(score_path, "w") as f:
|
||||||
json.dump(d_notes, f, indent=4)
|
json.dump(d_notes, f, indent=4)
|
||||||
|
|
@ -289,6 +299,18 @@ def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_laye
|
||||||
|
|
||||||
full_img.save(os.path.join(output_dir, "Concat.jpg"))
|
full_img.save(os.path.join(output_dir, "Concat.jpg"))
|
||||||
print(f" Saved regenerated Concat.jpg")
|
print(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)
|
||||||
|
full_img = Image.new("RGB", (max_w, total_h), "white")
|
||||||
|
|
||||||
|
y = 0
|
||||||
|
for img in concat_list_F:
|
||||||
|
full_img.paste(img, (0, y))
|
||||||
|
y += img.height
|
||||||
|
|
||||||
|
full_img.save(os.path.join(output_dir, "Concat_F.jpg"))
|
||||||
|
print(f" Saved regenerated Concat_F.jpg")
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,13 +65,15 @@ def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, la
|
||||||
|
|
||||||
# --- 2. Process Images (Regenerate & Concatenate) ---
|
# --- 2. Process Images (Regenerate & Concatenate) ---
|
||||||
concat_list = []
|
concat_list = []
|
||||||
|
concat_list_F = []
|
||||||
d_notes = dict.fromkeys(all_labels, "")
|
d_notes = dict.fromkeys(all_labels, "")
|
||||||
|
|
||||||
# Iterate over all labels naturally to assemble a complete student profile
|
# Iterate over all labels naturally to assemble a complete student profile
|
||||||
sorted_labels = sorted(labels_data.items(), key=lambda x: natural_key(x[0]))
|
sorted_labels = sorted(labels_data.items(), key=lambda x: natural_key(x[0]))
|
||||||
|
|
||||||
for label, content in sorted_labels:
|
for label, content in sorted_labels:
|
||||||
d_notes[label] = str(content['result'].get('score', 0))
|
result = content['result']
|
||||||
|
d_notes[label] = str(result.get('score', 0))
|
||||||
|
|
||||||
pdf_path = os.path.join(root_dir, f"Copie{student_id}", f"{label}.pdf")
|
pdf_path = os.path.join(root_dir, f"Copie{student_id}", f"{label}.pdf")
|
||||||
if not os.path.exists(pdf_path): continue
|
if not os.path.exists(pdf_path): continue
|
||||||
|
|
@ -102,6 +104,16 @@ def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, la
|
||||||
|
|
||||||
concat_list.append(final_img)
|
concat_list.append(final_img)
|
||||||
|
|
||||||
|
perfect_no_comment = True
|
||||||
|
if float(d_notes[label]) != 4.0:
|
||||||
|
perfect_no_comment = False
|
||||||
|
else:
|
||||||
|
if len(result.get('feedback', [])) != 0:
|
||||||
|
perfect_no_comment = False
|
||||||
|
if not perfect_no_comment:
|
||||||
|
concat_list_F.append(final_img)
|
||||||
|
|
||||||
|
|
||||||
# --- 3. Save Final Outputs ---
|
# --- 3. Save Final Outputs ---
|
||||||
with open(score_path, "w") as f:
|
with open(score_path, "w") as f:
|
||||||
json.dump(d_notes, f, indent=4)
|
json.dump(d_notes, f, indent=4)
|
||||||
|
|
@ -119,6 +131,18 @@ def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, la
|
||||||
|
|
||||||
full_img.save(os.path.join(output_dir, "Concat.jpg"))
|
full_img.save(os.path.join(output_dir, "Concat.jpg"))
|
||||||
print(f" Saved regenerated Concat.jpg")
|
print(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)
|
||||||
|
full_img = Image.new("RGB", (max_w, total_h), "white")
|
||||||
|
|
||||||
|
y = 0
|
||||||
|
for img in concat_list_F:
|
||||||
|
full_img.paste(img, (0, y))
|
||||||
|
y += img.height
|
||||||
|
|
||||||
|
full_img.save(os.path.join(output_dir, "Concat_F.jpg"))
|
||||||
|
print(f" Saved regenerated Concat_F.jpg")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue