small fixes
This commit is contained in:
+8
-6
@@ -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: <08-08-26 13:18>
|
# Time-stamp: <08-08-26 16:44>
|
||||||
#+OPTIONS:
|
#+OPTIONS:
|
||||||
|
|
||||||
* Méta
|
* Méta
|
||||||
@@ -149,6 +149,8 @@ Mettre les copies scannées au format pdf dans =Interro=.
|
|||||||
Découpe la partie gauche des copies, là où il devrait y avoir les
|
Découpe la partie gauche des copies, là où il devrait y avoir les
|
||||||
labels des exercices/questions.
|
labels des exercices/questions.
|
||||||
|
|
||||||
|
=python cutleft.py Interro --fullpage= to use the full page always.
|
||||||
|
|
||||||
Rerun on a single file with =python cutleft.py Interro/Copies/Copie01.pdf=
|
Rerun on a single file with =python cutleft.py Interro/Copies/Copie01.pdf=
|
||||||
|
|
||||||
** Labelisation et regroupement
|
** Labelisation et regroupement
|
||||||
@@ -174,11 +176,11 @@ Set proxy with ~export HTTPS_PROXY="http://10.0.0.1:3128"~
|
|||||||
=python plotting.py Interro/Copies/Copie01.pdf=
|
=python plotting.py Interro/Copies/Copie01.pdf=
|
||||||
|
|
||||||
It also generates les =Copie01.json=, à partir des =Copie01_01.json=
|
It also generates les =Copie01.json=, à partir des =Copie01_01.json=
|
||||||
1. En cas de soucis, (par exemple les pages ne sont pas dans le bon ordre)
|
En cas de soucis, (par exemple les pages ne sont pas dans le bon ordre)
|
||||||
- Réordonner les pages du fichier pdf
|
- Réordonner les pages du fichier pdf
|
||||||
- Rerun =python cutleft.py Interro/Copie{id}=
|
- Rerun =python cutleft.py Interro/Copie{id}=
|
||||||
- Rerun =python gemini_dir_batching.py Interro/Copie{id}= ?? À
|
- Rerun =python gemini_dir_batching.py Interro/Copie{id}=
|
||||||
vérifier, pas sûr que ça marche.
|
?? À vérifier, pas sûr que ça marche.
|
||||||
3. =python splitting_int.py Interro=
|
3. =python splitting_int.py Interro=
|
||||||
|
|
||||||
Découpe les copies suivant les exercices
|
Découpe les copies suivant les exercices
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import utils
|
||||||
import shutil
|
import shutil
|
||||||
import argparse
|
import argparse
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
@@ -124,6 +125,7 @@ def main():
|
|||||||
with open(label_groups, 'w') as f:
|
with open(label_groups, 'w') as f:
|
||||||
for items in groups.values():
|
for items in groups.values():
|
||||||
f.write(",".join(items) + "\n")
|
f.write(",".join(items) + "\n")
|
||||||
|
utils.edit_file_and_enter(label_groups)
|
||||||
|
|
||||||
with open(label_groups, "r") as f:
|
with open(label_groups, "r") as f:
|
||||||
lines = [line.strip() for line in f if line.strip()]
|
lines = [line.strip() for line in f if line.strip()]
|
||||||
|
|||||||
+18
-23
@@ -3,6 +3,7 @@ from functools import lru_cache
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import json # Added for schema output
|
import json # Added for schema output
|
||||||
|
import argparse
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from queue import Queue, Empty
|
from queue import Queue, Empty
|
||||||
@@ -14,10 +15,13 @@ DELIMITER_WIDTH = 5
|
|||||||
DELIMITER_COLOR = (0, 0, 0)
|
DELIMITER_COLOR = (0, 0, 0)
|
||||||
OUTPUT_SIZE = (1800, 1000)
|
OUTPUT_SIZE = (1800, 1000)
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
parser = argparse.ArgumentParser(description="PDF Cropper")
|
||||||
sys.exit("Usage: python script.py <directory_path_or_file_path>")
|
parser.add_argument("path", help="Directory path or PDF file path")
|
||||||
|
parser.add_argument("--fullpage", action="store_true", help="Process all files in full page mode (1 page per output file)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
path_arg = sys.argv[1]
|
path_arg = args.path
|
||||||
|
fullpage_mode = args.fullpage
|
||||||
files = []
|
files = []
|
||||||
INPUT_DIR = ""
|
INPUT_DIR = ""
|
||||||
COPIES_DIR = ""
|
COPIES_DIR = ""
|
||||||
@@ -121,7 +125,6 @@ def process_single_pdf(filename, shift_offset=0, max_per_file=5):
|
|||||||
Converts PDF to stitched images.
|
Converts PDF to stitched images.
|
||||||
Returns a tuple: (preview_image_resized, list_of_split_images, schema_dict)
|
Returns a tuple: (preview_image_resized, list_of_split_images, schema_dict)
|
||||||
"""
|
"""
|
||||||
# pdf_path = os.path.join(INPUT_DIR, filename)
|
|
||||||
try:
|
try:
|
||||||
pages = get_pdf_pages(filename)
|
pages = get_pdf_pages(filename)
|
||||||
cropped_images = []
|
cropped_images = []
|
||||||
@@ -164,7 +167,6 @@ def process_single_pdf(filename, shift_offset=0, max_per_file=5):
|
|||||||
|
|
||||||
# 3. Generate Preview (All stitched together, Resized)
|
# 3. Generate Preview (All stitched together, Resized)
|
||||||
full_stitch = stitch_images(cropped_images)
|
full_stitch = stitch_images(cropped_images)
|
||||||
# preview_resized = full_stitch.resize(OUTPUT_SIZE, Image.LANCZOS)
|
|
||||||
preview_resized = full_stitch.resize(OUTPUT_SIZE, Image.BILINEAR)
|
preview_resized = full_stitch.resize(OUTPUT_SIZE, Image.BILINEAR)
|
||||||
|
|
||||||
schema = {
|
schema = {
|
||||||
@@ -190,21 +192,15 @@ def save_results(result_tuple, filename):
|
|||||||
# --- Cleanup: Delete existing files for this PDF ---
|
# --- Cleanup: Delete existing files for this PDF ---
|
||||||
for f in os.listdir(OUTPUT_DIR):
|
for f in os.listdir(OUTPUT_DIR):
|
||||||
file_path = os.path.join(OUTPUT_DIR, f)
|
file_path = os.path.join(OUTPUT_DIR, f)
|
||||||
# 1. Delete schema file
|
|
||||||
if f == f"{base_name}_schema.json":
|
if f == f"{base_name}_schema.json":
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
# 2. Delete image files (pattern: basename_01.jpg, etc.)
|
|
||||||
elif f.startswith(f"{base_name}_") and f.endswith(".jpg"):
|
elif f.startswith(f"{base_name}_") and f.endswith(".jpg"):
|
||||||
# Check if the suffix is strictly numeric (e.g. "01") to avoid
|
|
||||||
# deleting unrelated files like "file_v2_01.jpg" when processing "file.pdf"
|
|
||||||
suffix = f[len(base_name)+1:-4]
|
suffix = f[len(base_name)+1:-4]
|
||||||
if suffix.isdigit():
|
if suffix.isdigit():
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
# ---------------------------------------------------
|
|
||||||
|
|
||||||
# Save Images
|
# Save Images
|
||||||
for i, img in enumerate(splits):
|
for i, img in enumerate(splits):
|
||||||
# Suffix _01, _02, etc.
|
|
||||||
suffix = f"_{i+1:02d}"
|
suffix = f"_{i+1:02d}"
|
||||||
output_filename = f"{base_name}{suffix}.jpg"
|
output_filename = f"{base_name}{suffix}.jpg"
|
||||||
output_path = os.path.join(OUTPUT_DIR, output_filename)
|
output_path = os.path.join(OUTPUT_DIR, output_filename)
|
||||||
@@ -217,14 +213,17 @@ def save_results(result_tuple, filename):
|
|||||||
with open(json_path, 'w') as f:
|
with open(json_path, 'w') as f:
|
||||||
json.dump(schema, f, indent=4)
|
json.dump(schema, f, indent=4)
|
||||||
print(f"Saved schema: {json_filename}")
|
print(f"Saved schema: {json_filename}")
|
||||||
|
|
||||||
|
|
||||||
# --- GUI Application ---
|
# --- GUI Application ---
|
||||||
|
|
||||||
class ImageReviewer:
|
class ImageReviewer:
|
||||||
def __init__(self, file_list):
|
def __init__(self, file_list, default_max_per_file=5):
|
||||||
self.files = file_list
|
self.files = file_list
|
||||||
self.index = 0
|
self.index = 0
|
||||||
self.current_shift = 0
|
self.current_shift = 0
|
||||||
self.current_max_per_file = 5
|
self.default_max_per_file = default_max_per_file
|
||||||
|
self.current_max_per_file = default_max_per_file
|
||||||
self.current_preview = None # Only stores the resized preview for GUI
|
self.current_preview = None # Only stores the resized preview for GUI
|
||||||
self.is_processing = False
|
self.is_processing = False
|
||||||
|
|
||||||
@@ -247,8 +246,7 @@ class ImageReviewer:
|
|||||||
self.root.bind('n', lambda e: self.on_shift(50))
|
self.root.bind('n', lambda e: self.on_shift(50))
|
||||||
self.root.bind('N', lambda e: self.on_shift(100))
|
self.root.bind('N', lambda e: self.on_shift(100))
|
||||||
self.root.bind('t', lambda e: self.on_shift(-50))
|
self.root.bind('t', lambda e: self.on_shift(-50))
|
||||||
self.root.bind('1', lambda e: self.on_set_max_pages(1)) # New Binding
|
self.root.bind('1', lambda e: self.on_set_max_pages(1))
|
||||||
|
|
||||||
|
|
||||||
# Start background pre-fetcher
|
# Start background pre-fetcher
|
||||||
self.bg_thread = Thread(target=self.prefetch_worker, daemon=True)
|
self.bg_thread = Thread(target=self.prefetch_worker, daemon=True)
|
||||||
@@ -266,7 +264,6 @@ class ImageReviewer:
|
|||||||
return
|
return
|
||||||
self.current_max_per_file = count
|
self.current_max_per_file = count
|
||||||
print(f"Setting max pages per file: {count}")
|
print(f"Setting max pages per file: {count}")
|
||||||
# Trigger reprocessing with current settings
|
|
||||||
self.trigger_processing(self.files[self.index], self.current_shift)
|
self.trigger_processing(self.files[self.index], self.current_shift)
|
||||||
|
|
||||||
def prefetch_worker(self):
|
def prefetch_worker(self):
|
||||||
@@ -276,7 +273,7 @@ class ImageReviewer:
|
|||||||
target = self.index + 1
|
target = self.index + 1
|
||||||
if target < len(self.files) and target != idx_to_process:
|
if target < len(self.files) and target != idx_to_process:
|
||||||
fname = self.files[target]
|
fname = self.files[target]
|
||||||
get_pdf_pages(fname) # Just calling it warms the lru_cache
|
get_pdf_pages(fname)
|
||||||
idx_to_process = target
|
idx_to_process = target
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|
||||||
@@ -290,7 +287,6 @@ class ImageReviewer:
|
|||||||
self.is_processing = False
|
self.is_processing = False
|
||||||
self.current_shift = 0
|
self.current_shift = 0
|
||||||
|
|
||||||
# Always trigger processing. If prefetched, get_pdf_pages returns instantly.
|
|
||||||
self.trigger_processing(filename, self.current_shift)
|
self.trigger_processing(filename, self.current_shift)
|
||||||
|
|
||||||
def trigger_processing(self, filename, shift):
|
def trigger_processing(self, filename, shift):
|
||||||
@@ -317,7 +313,6 @@ class ImageReviewer:
|
|||||||
self.load_current_image(use_prefetch=True)
|
self.load_current_image(use_prefetch=True)
|
||||||
self.is_processing = False
|
self.is_processing = False
|
||||||
except Empty:
|
except Empty:
|
||||||
# Check again in 100ms
|
|
||||||
self.root.after(100, lambda: self.check_manual_queue(filename))
|
self.root.after(100, lambda: self.check_manual_queue(filename))
|
||||||
|
|
||||||
def handle_processing_result(self, result, filename):
|
def handle_processing_result(self, result, filename):
|
||||||
@@ -325,7 +320,6 @@ class ImageReviewer:
|
|||||||
preview, splits, schema = result
|
preview, splits, schema = result
|
||||||
self.current_preview = preview
|
self.current_preview = preview
|
||||||
|
|
||||||
# Save in a background thread so the GUI updates instantly
|
|
||||||
Thread(target=save_results, args=(result, filename), daemon=True).start()
|
Thread(target=save_results, args=(result, filename), daemon=True).start()
|
||||||
|
|
||||||
self.update_display(filename, schema)
|
self.update_display(filename, schema)
|
||||||
@@ -350,7 +344,7 @@ class ImageReviewer:
|
|||||||
|
|
||||||
def on_shift(self, amount):
|
def on_shift(self, amount):
|
||||||
if self.is_processing:
|
if self.is_processing:
|
||||||
return # Ignore keys while processing
|
return
|
||||||
self.current_shift += amount
|
self.current_shift += amount
|
||||||
print(f"Applying shift: {self.current_shift}")
|
print(f"Applying shift: {self.current_shift}")
|
||||||
self.trigger_processing(self.files[self.index], self.current_shift)
|
self.trigger_processing(self.files[self.index], self.current_shift)
|
||||||
@@ -360,12 +354,13 @@ class ImageReviewer:
|
|||||||
return
|
return
|
||||||
self.index += 1
|
self.index += 1
|
||||||
self.current_shift = 0
|
self.current_shift = 0
|
||||||
self.current_max_per_file = 5 # Reset to default
|
self.current_max_per_file = self.default_max_per_file
|
||||||
self.load_current_image(use_prefetch=True)
|
self.load_current_image(use_prefetch=True)
|
||||||
|
|
||||||
|
|
||||||
# --- Entry Point ---
|
# --- Entry Point ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if not files:
|
if not files:
|
||||||
print("No PDF files found.")
|
print("No PDF files found.")
|
||||||
else:
|
else:
|
||||||
app = ImageReviewer(files)
|
app = ImageReviewer(files, default_max_per_file=1 if fullpage_mode else 5)
|
||||||
|
|||||||
+7
-18
@@ -1,6 +1,6 @@
|
|||||||
import shlex
|
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
|
import utils
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
@@ -119,7 +119,7 @@ PROMPT_3 = """I am providing:
|
|||||||
2. The source code of the exam questions (`enonce` file).
|
2. The source code of the exam questions (`enonce` file).
|
||||||
|
|
||||||
Your task:
|
Your task:
|
||||||
Extract important information necessary to understand the questions (e.g., definitions of objects, global notations, hypotheses, context) that are NOT part of the question texts themselves. Often, this information can be in a previous \\item that is not itself a question, but contains the question items.
|
Extract important information necessary to understand the questions (e.g., definitions of objects, global notations, hypotheses, context) that are NOT part of the question texts themselves. Often, this information can be in a previous \\item that is not itself a question, but contains the question items.
|
||||||
|
|
||||||
For example, given LaTeX code like
|
For example, given LaTeX code like
|
||||||
|
|
||||||
@@ -148,7 +148,9 @@ def process_exam(folder_path: str, restart: bool = False):
|
|||||||
folder = Path(folder_path)
|
folder = Path(folder_path)
|
||||||
|
|
||||||
cache_dir = folder / "Cache"
|
cache_dir = folder / "Cache"
|
||||||
|
tmp_dir = folder / "Tmp"
|
||||||
cache_dir.mkdir(exist_ok=True)
|
cache_dir.mkdir(exist_ok=True)
|
||||||
|
tmp_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
cache_q_file = cache_dir / "gemini_questions.json"
|
cache_q_file = cache_dir / "gemini_questions.json"
|
||||||
cache_s_file = cache_dir / "gemini_solutions.json"
|
cache_s_file = cache_dir / "gemini_solutions.json"
|
||||||
@@ -341,8 +343,8 @@ def process_exam(folder_path: str, restart: bool = False):
|
|||||||
# ==========================================
|
# ==========================================
|
||||||
# INITIAL GROUPING COMPUTATION
|
# INITIAL GROUPING COMPUTATION
|
||||||
# ==========================================
|
# ==========================================
|
||||||
items_file = folder / "exam_items.txt"
|
items_file = tmp_dir / "exam_items.txt"
|
||||||
full_items_file = folder / "exam_items_full.txt"
|
full_items_file = tmp_dir / "exam_items_full.txt"
|
||||||
trunc_map = {}
|
trunc_map = {}
|
||||||
|
|
||||||
# --- INITIAL GROUPING COMPUTATION ---
|
# --- INITIAL GROUPING COMPUTATION ---
|
||||||
@@ -469,20 +471,7 @@ def process_exam(folder_path: str, restart: bool = False):
|
|||||||
# --- OPEN EDITOR AND PARSE ---
|
# --- OPEN EDITOR AND PARSE ---
|
||||||
while True:
|
while True:
|
||||||
print("Opening items file for editing...")
|
print("Opening items file for editing...")
|
||||||
editor = os.environ.get("EDITOR")
|
utils.edit_file_and_enter(items_file)
|
||||||
try:
|
|
||||||
if editor:
|
|
||||||
subprocess.run(shlex.split(editor) + [str(items_file)])
|
|
||||||
else:
|
|
||||||
if sys.platform.startswith("linux"):
|
|
||||||
subprocess.run(["xdg-open", str(items_file)])
|
|
||||||
elif sys.platform == "darwin":
|
|
||||||
subprocess.run(["open", str(items_file)])
|
|
||||||
else:
|
|
||||||
os.startfile(str(items_file))
|
|
||||||
input("Press ENTER here once you have saved and closed the text file...")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error running editor: {e}")
|
|
||||||
|
|
||||||
print(f"Parsing edited items from {items_file.name}...")
|
print(f"Parsing edited items from {items_file.name}...")
|
||||||
with open(items_file, "r", encoding="utf-8") as f:
|
with open(items_file, "r", encoding="utf-8") as f:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ def generate_request(file, labels, names, context_labels, wrong_labels):
|
|||||||
parser = argparse.ArgumentParser(description="Process a directory or specific files using Gemini.")
|
parser = argparse.ArgumentParser(description="Process a directory or specific files using Gemini.")
|
||||||
parser.add_argument("input_paths", nargs='+', help="The input directory or specific files")
|
parser.add_argument("input_paths", nargs='+', help="The input directory or specific files")
|
||||||
parser.add_argument("--overwrite", action="store_true", help="Regenerate output even if it exists")
|
parser.add_argument("--overwrite", action="store_true", help="Regenerate output even if it exists")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# input_arg = Path(args.input_path)
|
# input_arg = Path(args.input_path)
|
||||||
|
|||||||
@@ -23,6 +23,24 @@ def enonce_total(base_dir):
|
|||||||
return "".join(output)
|
return "".join(output)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
def edit_file_and_enter(file):
|
||||||
|
editor = os.environ.get("EDITOR")
|
||||||
|
try:
|
||||||
|
if editor:
|
||||||
|
subprocess.run(shlex.split(editor) + [str(file)])
|
||||||
|
else:
|
||||||
|
if sys.platform.startswith("linux"):
|
||||||
|
subprocess.run(["xdg-open", str(file)])
|
||||||
|
elif sys.platform == "darwin":
|
||||||
|
subprocess.run(["open", str(file)])
|
||||||
|
else:
|
||||||
|
os.startfile(str(file))
|
||||||
|
input("Press ENTER here once you have saved and closed the text file...")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error running editor: {e}")
|
||||||
|
|
||||||
|
|
||||||
def pdf_image_of_enonce(root_dir, label):
|
def pdf_image_of_enonce(root_dir, label):
|
||||||
pdf_path = os.path.join(root_dir, "Text2", f"{label}.pdf")
|
pdf_path = os.path.join(root_dir, "Text2", f"{label}.pdf")
|
||||||
|
|||||||
Reference in New Issue
Block a user