diff --git a/from_tablette.py b/from_tablette.py
new file mode 100644
index 0000000..d2bef8c
--- /dev/null
+++ b/from_tablette.py
@@ -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
")
+ sys.exit(1)
+
+ sync_annotated(sys.argv[1])
diff --git a/reading_annotations.py b/reading_annotations.py
index c599988..11eb966 100644
--- a/reading_annotations.py
+++ b/reading_annotations.py
@@ -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):
diff --git a/reading_grouped_annotations.py b/reading_grouped_annotations.py
new file mode 100644
index 0000000..4691944
--- /dev/null
+++ b/reading_grouped_annotations.py
@@ -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 ")
+ 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
+ )
diff --git a/to_tablette.py b/to_tablette.py
new file mode 100644
index 0000000..f7af285
--- /dev/null
+++ b/to_tablette.py
@@ -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 ")
+ sys.exit(1)
+
+ process_directory(sys.argv[1])