reading grouped annotations ; to and from tablette
parent
7a43be3ac1
commit
9acdb66bab
|
|
@ -0,0 +1,30 @@
|
|||
import sys
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
def sync_annotated(dir_arg):
|
||||
bgnot_dir = Path(dir_arg) / "BGnot"
|
||||
annotated_dir = Path.home() / "SyncCopies" / "Annotées"
|
||||
|
||||
if not annotated_dir.is_dir():
|
||||
print(f"Error: Directory {annotated_dir} does not exist.")
|
||||
return
|
||||
|
||||
# Iterate over all PDF files in the annotated directory
|
||||
for pdf_file in annotated_dir.glob("*.pdf"):
|
||||
subdir_name = pdf_file.stem # 'f' from 'f.pdf'
|
||||
target_subdir = bgnot_dir / subdir_name
|
||||
|
||||
if not target_subdir.is_dir():
|
||||
print(f"Warning: Directory {target_subdir} not found.")
|
||||
else:
|
||||
dest_file = target_subdir / "Concat_annotated.pdf"
|
||||
print("copying ", pdf_file, " to ", dest_file)
|
||||
shutil.copy2(pdf_file, dest_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python script.py <dir>")
|
||||
sys.exit(1)
|
||||
|
||||
sync_annotated(sys.argv[1])
|
||||
|
|
@ -27,7 +27,7 @@ def detect_checks_and_notes(output_dir):
|
|||
json_path = os.path.join(output_dir, "checkboxes.json")
|
||||
|
||||
if not (os.path.exists(pdf_path) and os.path.exists(ref_path)):
|
||||
print(f"Missing files in {output_dir}")
|
||||
print(f"\tMissing annotated file in {output_dir}")
|
||||
return [], None
|
||||
|
||||
# Load Coordinates
|
||||
|
|
@ -159,8 +159,8 @@ def has_significant_notes(note_img, threshold=20):
|
|||
# Count pixels with significant opacity
|
||||
visible_pixels = np.sum(alpha > 50)
|
||||
# visible_pixels_bis = np.sum(alpha > 200)
|
||||
if visible_pixels > 0:
|
||||
print(f"Debug : visible pixels is {visible_pixels}")
|
||||
# if visible_pixels > 0:
|
||||
# print(f"Debug : visible pixels is {visible_pixels}")
|
||||
return visible_pixels > threshold
|
||||
|
||||
def apply_actions_and_regenerate(root_dir, data, student_id, actions, notes_layer, all_labels):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
import collections
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
import annotating
|
||||
from annotating_with_checks import natural_key
|
||||
from reading_annotations import detect_checks_and_notes, has_significant_notes
|
||||
|
||||
def apply_actions_and_regenerate_grouped(root_dir, data, student_id, actions, label_notes, all_labels):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
output_dir = os.path.join(root_dir, "BGnot", f"Copie{student_id}")
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
score_path = os.path.join(output_dir, "score.json")
|
||||
labels_data = data.get(student_id, {})
|
||||
|
||||
# --- 1. Apply Actions to Data (Update scores / Flags for deletion) ---
|
||||
actions_by_label = collections.defaultdict(list)
|
||||
for a in actions:
|
||||
actions_by_label[a['label']].append(a)
|
||||
|
||||
dirty_labels = set()
|
||||
|
||||
for label, acts in actions_by_label.items():
|
||||
if label not in labels_data: continue
|
||||
|
||||
content = labels_data[label]
|
||||
result = content['result']
|
||||
feedbacks = result.get('feedback', [])
|
||||
|
||||
# Helpers to find objects by index
|
||||
global_fb = [f for f in feedbacks if not f.get('box_2d')]
|
||||
local_fb = [f for f in feedbacks if f.get('box_2d')]
|
||||
local_fb.sort(key=lambda x: x['box_2d'][0])
|
||||
|
||||
for act in acts:
|
||||
if act['type'] == 'score':
|
||||
result['score'] = act['value']
|
||||
dirty_labels.add(label)
|
||||
print(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}")
|
||||
|
||||
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}")
|
||||
else:
|
||||
target["norectangle"] = True
|
||||
print(f" > Deleted rect in {label}")
|
||||
dirty_labels.add(label)
|
||||
|
||||
# --- 2. Process Images (Regenerate & Concatenate) ---
|
||||
concat_list = []
|
||||
d_notes = dict.fromkeys(all_labels, "")
|
||||
|
||||
# Iterate over all labels naturally to assemble a complete student profile
|
||||
sorted_labels = sorted(labels_data.items(), key=lambda x: natural_key(x[0]))
|
||||
|
||||
for label, content in sorted_labels:
|
||||
d_notes[label] = str(content['result'].get('score', 0))
|
||||
|
||||
pdf_path = os.path.join(root_dir, f"Copie{student_id}", f"{label}.pdf")
|
||||
if not os.path.exists(pdf_path): continue
|
||||
|
||||
(base_img, _, _) = annotating.make_base_image(pdf_path)
|
||||
|
||||
# Compose uses the result object we modified in step 1
|
||||
final_img, _ = annotating.compose_label_image(
|
||||
base_img, label, content['result'], content['coordinates'][0],
|
||||
with_error=False
|
||||
)
|
||||
if final_img is None:
|
||||
continue
|
||||
|
||||
# Overlay manual notes specific to this label
|
||||
has_notes = False
|
||||
if label in label_notes:
|
||||
sub_note = label_notes[label]
|
||||
if has_significant_notes(sub_note):
|
||||
has_notes = True
|
||||
final_img.paste(sub_note, (0, 0), mask=sub_note)
|
||||
|
||||
# Save individual file if Modified (Dirty logic or visual notes)
|
||||
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")
|
||||
|
||||
concat_list.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}")
|
||||
|
||||
if concat_list:
|
||||
max_w = max(i.width for i in concat_list)
|
||||
total_h = sum(i.height for i in concat_list)
|
||||
full_img = Image.new("RGB", (max_w, total_h), "white")
|
||||
|
||||
y = 0
|
||||
for img in concat_list:
|
||||
full_img.paste(img, (0, y))
|
||||
y += img.height
|
||||
|
||||
full_img.save(os.path.join(output_dir, "Concat.jpg"))
|
||||
print(f" Saved regenerated Concat.jpg")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python reading_grouped_annotations.py <Dir>")
|
||||
sys.exit(1)
|
||||
|
||||
root_dir = sys.argv[1]
|
||||
bgnot_dir = os.path.join(root_dir, "BGnot")
|
||||
|
||||
if not os.path.exists(bgnot_dir):
|
||||
print(f"Directory {bgnot_dir} does not exist. Run annotating_by_label.py first.")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
all_labels = sorted(list(filter(None,
|
||||
(Path(root_dir) / "labels")
|
||||
.read_text().splitlines())),
|
||||
key=natural_key)
|
||||
except FileNotFoundError:
|
||||
all_labels = []
|
||||
|
||||
# Load original data
|
||||
original_data = annotating.make_dictionary(root_dir)
|
||||
|
||||
actions_by_student = collections.defaultdict(list)
|
||||
notes_by_student = collections.defaultdict(dict)
|
||||
|
||||
# --- 1. Scan BGnot grouped directories and extract all checks & notes ---
|
||||
for entry in os.listdir(bgnot_dir):
|
||||
gdir = os.path.join(bgnot_dir, entry)
|
||||
|
||||
if not os.path.isdir(gdir) or entry.startswith("Copie"):
|
||||
continue # Ignore files and already compiled student folders
|
||||
|
||||
print(f"\nScanning grouped annotations in {entry}")
|
||||
actions, notes_img = detect_checks_and_notes(gdir)
|
||||
|
||||
bnote_path = os.path.join(gdir, "bnote.json")
|
||||
if not os.path.exists(bnote_path) or notes_img is None:
|
||||
continue
|
||||
|
||||
with open(bnote_path, "r") as f:
|
||||
bnote_data = json.load(f)
|
||||
|
||||
# Route actions to specific students
|
||||
for act in actions:
|
||||
sid = str(act.get("student_id"))
|
||||
if sid:
|
||||
actions_by_student[sid].append(act)
|
||||
|
||||
# Route manual note crops to specific students and labels
|
||||
for img_info in bnote_data.get("images", []):
|
||||
sid = str(img_info.get("id"))
|
||||
lbl = img_info.get("label")
|
||||
hmin = img_info.get("hmin", 0)
|
||||
hmax = img_info.get("hmax", 0)
|
||||
|
||||
if hmax > hmin:
|
||||
crop = notes_img.crop((0, hmin, notes_img.width, hmax))
|
||||
# Store it if there are pen marks on it
|
||||
if has_significant_notes(crop):
|
||||
notes_by_student[sid][lbl] = crop
|
||||
|
||||
# --- 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):
|
||||
if sid not in original_data:
|
||||
continue
|
||||
|
||||
print(f"\nProcessing compilation for: Copie{sid}")
|
||||
apply_actions_and_regenerate_grouped(
|
||||
root_dir,
|
||||
original_data,
|
||||
sid,
|
||||
actions_by_student[sid],
|
||||
notes_by_student[sid],
|
||||
all_labels
|
||||
)
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
def process_directory(dir_arg):
|
||||
# Résolution des chemins
|
||||
base_dir = Path(dir_arg)
|
||||
bgnot_dir = base_dir / "BGnot"
|
||||
sync_dir = Path.home() / "SyncCopies" / "À Annoter" / dir_arg
|
||||
|
||||
# Création du dossier de destination s'il n'existe pas
|
||||
sync_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not bgnot_dir.is_dir():
|
||||
print(f"Erreur : le sous-dossier {bgnot_dir} n'existe pas.")
|
||||
return
|
||||
|
||||
# Récupération de tous les sous-dossiers
|
||||
subdirs = [d for d in bgnot_dir.iterdir() if d.is_dir()]
|
||||
if not subdirs:
|
||||
return
|
||||
|
||||
# Application des règles de sélection
|
||||
all_start_with_copie = all(d.name.startswith("Copie") for d in subdirs)
|
||||
if all_start_with_copie:
|
||||
chosen_dirs = subdirs
|
||||
else:
|
||||
chosen_dirs = [d for d in subdirs if not d.name.startswith("Copie")]
|
||||
|
||||
# Traitement des dossiers sélectionnés
|
||||
for subdir in chosen_dirs:
|
||||
concat_file = subdir / "Concat.pdf"
|
||||
|
||||
if not concat_file.is_file():
|
||||
print(f"Attention : le fichier {concat_file} est introuvable.")
|
||||
continue
|
||||
|
||||
symlink_path = sync_dir / f"{subdir.name}.pdf"
|
||||
|
||||
# Supprime le lien précédent s'il existe pour éviter une erreur
|
||||
if symlink_path.is_symlink() or symlink_path.exists():
|
||||
symlink_path.unlink()
|
||||
|
||||
# Création du lien symbolique (pointe vers le chemin absolu pour éviter les problèmes)
|
||||
os.link(concat_file.absolute(), symlink_path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python script.py <dir>")
|
||||
sys.exit(1)
|
||||
|
||||
process_directory(sys.argv[1])
|
||||
Loading…
Reference in New Issue