Compare commits
No commits in common. "26d02b098774ff43f080b2bee05323d83dcd45a7" and "81be54b5f7f7aeb102da184b6570769816ac6cbd" have entirely different histories.
26d02b0987
...
81be54b5f7
13
Readme.org
13
Readme.org
|
|
@ -1,7 +1,7 @@
|
||||||
#+title: Script
|
#+title: Script
|
||||||
#+author: Sébastien Miquel
|
#+author: Sébastien Miquel
|
||||||
#+date: 14-03-2026
|
#+date: 14-03-2026
|
||||||
# Time-stamp: <07-05-26 11:33>
|
# Time-stamp: <01-05-26 22:35>
|
||||||
#+OPTIONS:
|
#+OPTIONS:
|
||||||
|
|
||||||
* Quézaco
|
* Quézaco
|
||||||
|
|
@ -205,8 +205,7 @@ OU
|
||||||
|
|
||||||
1. =python from_tablette.py Interro= (gestion perso)
|
1. =python from_tablette.py Interro= (gestion perso)
|
||||||
|
|
||||||
_Before_ : delete =~/SyncCopies/Annotées=, copy or sync from the
|
_Before_ : delete =~/SyncCopies/Annotées=, copy from the tablette to here.
|
||||||
tablette to here.
|
|
||||||
|
|
||||||
Une fois les corrections manuelles appliquées aux fichiers
|
Une fois les corrections manuelles appliquées aux fichiers
|
||||||
=Concat.pdf=, il faut enregistrer le fichier annoté au même endroit,
|
=Concat.pdf=, il faut enregistrer le fichier annoté au même endroit,
|
||||||
|
|
@ -243,13 +242,9 @@ OU
|
||||||
5. (gestion perso)
|
5. (gestion perso)
|
||||||
+ Deploy =miqmacs-copies-assets=, and
|
+ Deploy =miqmacs-copies-assets=, and
|
||||||
+ update the copies from =miqmacs.fr/admin=.
|
+ update the copies from =miqmacs.fr/admin=.
|
||||||
6. (gestion perso) Impression d'une copie. Via Evince » print to pdf.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* Recorrection d'une seule copie (peu testé)
|
* Recorrection d'une seule copie (peu testé)
|
||||||
|
|
||||||
|
|
||||||
!! Attention, refaire ne marchera pas si tu fais une annotation non
|
!! Attention, refaire ne marchera pas si tu fais une annotation non
|
||||||
groupée into refaire !!
|
groupée into refaire !!
|
||||||
|
|
||||||
|
|
@ -257,8 +252,8 @@ groupée into refaire !!
|
||||||
+ =python plotting.py InterroTest/Copie01.pdf=
|
+ =python plotting.py InterroTest/Copie01.pdf=
|
||||||
+ =python splitting_int.py InterroTest/Copie20.pdf=
|
+ =python splitting_int.py InterroTest/Copie20.pdf=
|
||||||
2. Créer =refaire.json=, avec un contenu comme
|
2. Créer =refaire.json=, avec un contenu comme
|
||||||
: [["Copie01", []],
|
[["Copie01", []],
|
||||||
: ["Copie01", ["Ex 1 : 1)"]]]
|
["Copie01", ["Ex 1 : 1)"]]]
|
||||||
3. Appeler =correction= avec --refaire. Il doit créer des groupes
|
3. Appeler =correction= avec --refaire. Il doit créer des groupes
|
||||||
individuels, faire des requêtes, et remplacer les corrections
|
individuels, faire des requêtes, et remplacer les corrections
|
||||||
précédentes (à sauver ailleurs).
|
précédentes (à sauver ailleurs).
|
||||||
|
|
|
||||||
|
|
@ -582,7 +582,7 @@ Here is a list of all possible labels. You need to answer with a list one of the
|
||||||
height = grouping.get_pdf_height(str(new_pdf_path))
|
height = grouping.get_pdf_height(str(new_pdf_path))
|
||||||
grouping.create_jpg(add_label, idx, [(pid, str(new_pdf_path), height)], INPUT_DIR)
|
grouping.create_jpg(add_label, idx, [(pid, str(new_pdf_path), height)], INPUT_DIR)
|
||||||
new_tasks.append((str(Path(INPUT_DIR) / add_label / f"Group_{idx+1}.jpg"),
|
new_tasks.append((str(Path(INPUT_DIR) / add_label / f"Group_{idx+1}.jpg"),
|
||||||
add_label, False, f"{label}(->)"))
|
add_label, False))
|
||||||
error += f"(->){add_label}"
|
error += f"(->){add_label}"
|
||||||
keep_error = True
|
keep_error = True
|
||||||
else:
|
else:
|
||||||
|
|
@ -603,7 +603,6 @@ def process_single_task(task_tuple, precomputed_response=None):
|
||||||
file_path = task_tuple[0]
|
file_path = task_tuple[0]
|
||||||
label = task_tuple[1]
|
label = task_tuple[1]
|
||||||
can_spawn_tasks = task_tuple[2] if len(task_tuple) > 2 else True
|
can_spawn_tasks = task_tuple[2] if len(task_tuple) > 2 else True
|
||||||
injected_error = task_tuple[3] if len(task_tuple) > 3 else ""
|
|
||||||
|
|
||||||
group_name = os.path.splitext(file_path)[0]
|
group_name = os.path.splitext(file_path)[0]
|
||||||
json_path = group_name + '.json'
|
json_path = group_name + '.json'
|
||||||
|
|
@ -649,15 +648,6 @@ def process_single_task(task_tuple, precomputed_response=None):
|
||||||
for p in json_data:
|
for p in json_data:
|
||||||
pid = p["id"]
|
pid = p["id"]
|
||||||
res = p["result"]
|
res = p["result"]
|
||||||
|
|
||||||
# Inject additional error if present
|
|
||||||
if injected_error:
|
|
||||||
if res["error"]:
|
|
||||||
res["error"] = f"{injected_error} {res['error']}"
|
|
||||||
else:
|
|
||||||
res["error"] = injected_error
|
|
||||||
|
|
||||||
|
|
||||||
yming, ymaxg, width_r = d_data[pid]
|
yming, ymaxg, width_r = d_data[pid]
|
||||||
|
|
||||||
pdf_path = Path(INPUT_DIR) / f"Copie{pid}" / f"{label}.pdf"
|
pdf_path = Path(INPUT_DIR) / f"Copie{pid}" / f"{label}.pdf"
|
||||||
|
|
@ -720,8 +710,6 @@ def process_single_task(task_tuple, precomputed_response=None):
|
||||||
tprint(f"Error decoding JSON for {file_path}", file=sys.stderr)
|
tprint(f"Error decoding JSON for {file_path}", file=sys.stderr)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Exception processing {file_path}: {e}"
|
error_msg = f"Exception processing {file_path}: {e}"
|
||||||
import traceback
|
|
||||||
traceback.print_exc() # <--- Add this line to see the real crash
|
|
||||||
print(error_msg, file=sys.stderr)
|
print(error_msg, file=sys.stderr)
|
||||||
with io_lock:
|
with io_lock:
|
||||||
errors_summary.append((error_msg, file_path))
|
errors_summary.append((error_msg, file_path))
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,7 @@ def compile_to_pdf(text, output_pdf_path): # 21 cm + 3.8 (dimension de la marge
|
||||||
def fetch_and_save_sub_text(ex_id, indices, label, text_path):
|
def fetch_and_save_sub_text(ex_id, indices, label, text_path):
|
||||||
"""Fetches text for a specific sub-question and saves it to Text/{label}.tex"""
|
"""Fetches text for a specific sub-question and saves it to Text/{label}.tex"""
|
||||||
qinds = ",".join(map(str, indices))
|
qinds = ",".join(map(str, indices))
|
||||||
if qinds:
|
|
||||||
url = f"http://localhost:8080/exercices/exo_q_text/{ex_id}/{qinds}"
|
url = f"http://localhost:8080/exercices/exo_q_text/{ex_id}/{qinds}"
|
||||||
else:
|
|
||||||
url = f"http://localhost:8080/exercices/exo_q_text/{ex_id}"
|
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(url) as response:
|
with urllib.request.urlopen(url) as response:
|
||||||
content = response.read().decode('utf-8')
|
content = response.read().decode('utf-8')
|
||||||
|
|
@ -74,10 +71,7 @@ def fetch_and_save_sub_text(ex_id, indices, label, text_path):
|
||||||
def fetch_and_save_sub_sol(ex_id, indices, label, sol_path):
|
def fetch_and_save_sub_sol(ex_id, indices, label, sol_path):
|
||||||
"""Fetches text for a specific sub-question and saves it to Text/{label}.tex"""
|
"""Fetches text for a specific sub-question and saves it to Text/{label}.tex"""
|
||||||
qinds = ",".join(map(str, indices))
|
qinds = ",".join(map(str, indices))
|
||||||
if qinds:
|
|
||||||
url = f"http://localhost:8080/exercices/exo_q_sol/{ex_id}/{qinds}"
|
url = f"http://localhost:8080/exercices/exo_q_sol/{ex_id}/{qinds}"
|
||||||
else:
|
|
||||||
url = f"http://localhost:8080/exercices/exo_q_sol/{ex_id}"
|
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(url) as response:
|
with urllib.request.urlopen(url) as response:
|
||||||
content = response.read().decode('utf-8')
|
content = response.read().decode('utf-8')
|
||||||
|
|
@ -174,11 +168,11 @@ def process_directory(directory):
|
||||||
if not tex_files:
|
if not tex_files:
|
||||||
print(f"No .tex file found in {directory}. Looking in /Staging/Interro/")
|
print(f"No .tex file found in {directory}. Looking in /Staging/Interro/")
|
||||||
int_name = directory[:-1] if directory.endswith("/") else directory
|
int_name = directory[:-1] if directory.endswith("/") else directory
|
||||||
tex_path = os.path.join(os.path.expanduser("~"), "Prépa/Staging/Interro", f"{int_name}.tex")
|
tex_path = os.path.join(os.path.expanduser("~"), "Prépa/Staging/Interro/", int_name, ".tex")
|
||||||
if os.path.exists(tex_path):
|
if os.path.exists(tex_path):
|
||||||
tex_file = tex_path
|
tex_file = tex_path
|
||||||
else:
|
else:
|
||||||
print("Not found in ", tex_path)
|
print("Not found.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
tex_file = tex_files[0]
|
tex_file = tex_files[0]
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ def process_copy_group(group_key, files):
|
||||||
|
|
||||||
# Run ThreadPool on GROUPS (Copies), not individual files
|
# Run ThreadPool on GROUPS (Copies), not individual files
|
||||||
# Each thread handles one student's full exam copy sequentially
|
# Each thread handles one student's full exam copy sequentially
|
||||||
with ThreadPoolExecutor(max_workers=16) as executor:
|
with ThreadPoolExecutor(max_workers=12) as executor:
|
||||||
# Convert dict items to arguments for map
|
# Convert dict items to arguments for map
|
||||||
# executor.map expects a function and an iterable.
|
# executor.map expects a function and an iterable.
|
||||||
# We use a lambda or separate function to unpack the tuple if needed,
|
# We use a lambda or separate function to unpack the tuple if needed,
|
||||||
|
|
|
||||||
|
|
@ -4358,8 +4358,6 @@ conjoncture
|
||||||
conjugaison
|
conjugaison
|
||||||
conjugal
|
conjugal
|
||||||
conjuguer
|
conjuguer
|
||||||
conjugué
|
|
||||||
conjugués
|
|
||||||
conjuration
|
conjuration
|
||||||
conjuré
|
conjuré
|
||||||
conjurer
|
conjurer
|
||||||
|
|
@ -6099,7 +6097,6 @@ détenus
|
||||||
détérioration
|
détérioration
|
||||||
détériorer
|
détériorer
|
||||||
déterminant
|
déterminant
|
||||||
déterminants
|
|
||||||
déterminante
|
déterminante
|
||||||
détermination
|
détermination
|
||||||
détermine
|
détermine
|
||||||
|
|
@ -10282,7 +10279,6 @@ inacceptable
|
||||||
inacceptables
|
inacceptables
|
||||||
inaccessible
|
inaccessible
|
||||||
inaccoutumé
|
inaccoutumé
|
||||||
inachevé
|
|
||||||
inactif
|
inactif
|
||||||
inaction
|
inaction
|
||||||
inadmissible
|
inadmissible
|
||||||
|
|
|
||||||
45
plotting.py
45
plotting.py
|
|
@ -9,7 +9,7 @@ from tkinter import messagebox
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from PIL import Image, ImageDraw, ImageFont, ImageTk
|
from PIL import Image, ImageDraw, ImageFont, ImageTk
|
||||||
|
|
||||||
print("o to open pdf, O original pdf, e to emacs part, p to go back, i to interro, click for coordinates")
|
print("o to open pdf, O original pdf, e to emacs part, i to interro, click for coordinates")
|
||||||
|
|
||||||
# --- Configuration & Globals ---
|
# --- Configuration & Globals ---
|
||||||
padding = 60
|
padding = 60
|
||||||
|
|
@ -79,8 +79,6 @@ def prepare_image(image_path: str, bounding_boxes, all_labels, nb_pages, last_la
|
||||||
current_index = all_labels.index(label)
|
current_index = all_labels.index(label)
|
||||||
if current_index < last_label_index or (last_label_index == -1 and current_index != 0):
|
if current_index < last_label_index or (last_label_index == -1 and current_index != 0):
|
||||||
color = "red"
|
color = "red"
|
||||||
elif current_index > last_label_index + 1:
|
|
||||||
color = "orange"
|
|
||||||
last_label_index = current_index
|
last_label_index = current_index
|
||||||
|
|
||||||
draw.rectangle(((abs_x_min, abs_y_min), (abs_x_max, abs_y_max)), outline=color, width=4)
|
draw.rectangle(((abs_x_min, abs_y_min), (abs_x_max, abs_y_max)), outline=color, width=4)
|
||||||
|
|
@ -172,15 +170,8 @@ class ImageViewer:
|
||||||
self.active_copie_name = None
|
self.active_copie_name = None
|
||||||
self.accumulated_results = None # Dict with "name" and "list"
|
self.accumulated_results = None # Dict with "name" and "list"
|
||||||
|
|
||||||
# To go back
|
|
||||||
self.history = []
|
|
||||||
self.forward_stack = []
|
|
||||||
self.current_pil_image = None
|
|
||||||
|
|
||||||
|
|
||||||
# Bindings
|
# Bindings
|
||||||
self.root.bind('<Return>', self.on_enter)
|
self.root.bind('<Return>', self.on_enter)
|
||||||
self.root.bind('p', self.on_previous)
|
|
||||||
self.root.bind('e', self.on_edit)
|
self.root.bind('e', self.on_edit)
|
||||||
self.root.bind('o', self.on_open_pdf)
|
self.root.bind('o', self.on_open_pdf)
|
||||||
self.root.bind('i', self.on_open_interro)
|
self.root.bind('i', self.on_open_interro)
|
||||||
|
|
@ -193,10 +184,6 @@ class ImageViewer:
|
||||||
def poll_queue(self):
|
def poll_queue(self):
|
||||||
if not self.is_viewing:
|
if not self.is_viewing:
|
||||||
try:
|
try:
|
||||||
# pil_image, json_path, metadata = image_queue.get_nowait()
|
|
||||||
if self.forward_stack:
|
|
||||||
pil_image, json_path, metadata = self.forward_stack.pop()
|
|
||||||
else:
|
|
||||||
pil_image, json_path, metadata = image_queue.get_nowait()
|
pil_image, json_path, metadata = image_queue.get_nowait()
|
||||||
|
|
||||||
# Handle End of Stream
|
# Handle End of Stream
|
||||||
|
|
@ -212,7 +199,6 @@ class ImageViewer:
|
||||||
# Start new batch
|
# Start new batch
|
||||||
self.active_copie_name = metadata["copie"]
|
self.active_copie_name = metadata["copie"]
|
||||||
self.accumulated_results = {"name": metadata["name"], "list": []}
|
self.accumulated_results = {"name": metadata["name"], "list": []}
|
||||||
self.history.clear()
|
|
||||||
|
|
||||||
self.display_image(pil_image, json_path, metadata)
|
self.display_image(pil_image, json_path, metadata)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
|
|
@ -228,24 +214,7 @@ class ImageViewer:
|
||||||
json.dump(self.accumulated_results, f)
|
json.dump(self.accumulated_results, f)
|
||||||
self.accumulated_results = None
|
self.accumulated_results = None
|
||||||
|
|
||||||
|
|
||||||
def on_previous(self, event):
|
|
||||||
if self.is_viewing and self.history:
|
|
||||||
print("Going back to previous image...")
|
|
||||||
prev_pil, prev_json, prev_meta, num_added = self.history.pop()
|
|
||||||
|
|
||||||
# Undo the accumulation to prevent duplicates when we hit Enter again
|
|
||||||
if self.accumulated_results and num_added > 0:
|
|
||||||
self.accumulated_results["list"] = self.accumulated_results["list"][:-num_added]
|
|
||||||
|
|
||||||
# Push current image to the forward stack so we don't lose it
|
|
||||||
self.forward_stack.append((self.current_pil_image,
|
|
||||||
self.current_json_path, self.current_meta))
|
|
||||||
|
|
||||||
# Display the previous image immediately
|
|
||||||
self.display_image(prev_pil, prev_json, prev_meta)
|
|
||||||
def display_image(self, pil_image, json_path, metadata):
|
def display_image(self, pil_image, json_path, metadata):
|
||||||
self.current_pil_image = pil_image # ADD THIS LINE
|
|
||||||
self.orig_size = pil_image.size
|
self.orig_size = pil_image.size
|
||||||
self.scale_factor = 1.0
|
self.scale_factor = 1.0
|
||||||
screen_h = self.root.winfo_screenheight() - 100
|
screen_h = self.root.winfo_screenheight() - 100
|
||||||
|
|
@ -264,7 +233,6 @@ class ImageViewer:
|
||||||
def on_enter(self, event):
|
def on_enter(self, event):
|
||||||
if self.is_viewing:
|
if self.is_viewing:
|
||||||
print(f"Committing data for {self.current_json_path.name}...")
|
print(f"Committing data for {self.current_json_path.name}...")
|
||||||
num_added = 0 # ADD THIS LINE
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(self.current_json_path, 'r') as f:
|
with open(self.current_json_path, 'r') as f:
|
||||||
|
|
@ -277,8 +245,6 @@ class ImageViewer:
|
||||||
self.current_meta["schema"]
|
self.current_meta["schema"]
|
||||||
)
|
)
|
||||||
|
|
||||||
num_added = len(converted_items)
|
|
||||||
|
|
||||||
# Add to accumulator
|
# Add to accumulator
|
||||||
if self.accumulated_results:
|
if self.accumulated_results:
|
||||||
self.accumulated_results["list"].extend(converted_items)
|
self.accumulated_results["list"].extend(converted_items)
|
||||||
|
|
@ -293,9 +259,6 @@ class ImageViewer:
|
||||||
messagebox.showerror("JSON Error", msg)
|
messagebox.showerror("JSON Error", msg)
|
||||||
return # Abort advancement
|
return # Abort advancement
|
||||||
|
|
||||||
self.history.append((self.current_pil_image, self.current_json_path,
|
|
||||||
self.current_meta, num_added))
|
|
||||||
|
|
||||||
# Advance UI
|
# Advance UI
|
||||||
self.is_viewing = False
|
self.is_viewing = False
|
||||||
self.label.config(image="", text="Loading next...")
|
self.label.config(image="", text="Loading next...")
|
||||||
|
|
@ -307,13 +270,13 @@ class ImageViewer:
|
||||||
print(f"Opening {pdf_path}")
|
print(f"Opening {pdf_path}")
|
||||||
subprocess.Popen(['xdg-open', str(pdf_path.absolute())])
|
subprocess.Popen(['xdg-open', str(pdf_path.absolute())])
|
||||||
|
|
||||||
def on_open_interro(self, event):
|
def on_open_ori_pdf(self, event):
|
||||||
if self.is_viewing and self.current_json_path:
|
if self.is_viewing and self.current_json_path:
|
||||||
pdf_path = "/home/sebastien/Prépa/Staging/Interro/" + str(base_dir) + ".pdf"
|
pdf_path = "/home/sebastien/Staging/Interro/" + str(base_dir) + "pdf"
|
||||||
print(f"Opening {pdf_path}")
|
print(f"Opening {pdf_path}")
|
||||||
subprocess.Popen(['xdg-open', pdf_path])
|
subprocess.Popen(['xdg-open', pdf_path])
|
||||||
|
|
||||||
def on_open_ori_pdf(self, event):
|
def on_open_interro(self, event):
|
||||||
if self.is_viewing and self.current_json_path:
|
if self.is_viewing and self.current_json_path:
|
||||||
new_filename = self.current_json_path.stem.split('_')[0] + ".pdf"
|
new_filename = self.current_json_path.stem.split('_')[0] + ".pdf"
|
||||||
pdf_path = self.current_json_path.parent / "Copies Originales" / new_filename
|
pdf_path = self.current_json_path.parent / "Copies Originales" / new_filename
|
||||||
|
|
|
||||||
5
utils.py
5
utils.py
|
|
@ -14,10 +14,7 @@ def enonce_total(base_dir):
|
||||||
if not text_dir.is_dir():
|
if not text_dir.is_dir():
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# Exclude .tex and .pdf files
|
files = [f for f in text_dir.iterdir() if f.is_file()]
|
||||||
files = [f for f in text_dir.iterdir()
|
|
||||||
if f.is_file() and f.suffix.lower() not in ('.tex', '.pdf')]
|
|
||||||
|
|
||||||
files.sort(key=lambda f: natural_key(f.name))
|
files.sort(key=lambda f: natural_key(f.name))
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue