Compare commits

..
2 Commits
Author SHA1 Message Date
sebastien 576e3c9452 Fix bug de tri des labels dans le fichier .json 2026-08-22 13:52:24 +02:00
sebastien 2921d5ec8e dependencies et .gitignore 2026-08-22 13:45:04 +02:00
4 changed files with 117 additions and 17 deletions
+10
View File
@@ -0,0 +1,10 @@
OLD/
__pycache__/
*.py[cod]
.venv/
build/
dist/
*.egg-info/
config.py
.copienator-gui.json
.copienator/
+63 -12
View File
@@ -33,26 +33,77 @@ question précédente ou autre.
Les labels de questions sont utilisés comme noms de fichiers. Les Les labels de questions sont utilisés comme noms de fichiers. Les
labels contenant notamment =:= ne sont pas acceptés par Windows. labels contenant notamment =:= ne sont pas acceptés par Windows.
** Requirements ** Prérequis et installation
*** Python *** Python 3.11 ou plus récent
Libraries : Créer et activer un environnement virtuel est recommandé :
#+BEGIN_SRC bash #+BEGIN_SRC bash
pip install numpy pandas matplotlib pillow pydantic pypdf pdf2image reportlab img2pdf pymupdf ftfy ezodf google python -m venv .venv
pip install -e .
#+END_SRC #+END_SRC
La seconde commande installe les exécutables =copienator= et Sous Linux :
=copienator-gui=. Sans installation, les mêmes commandes restent
accessibles avec =python -m copienator=.
*** Poppler (for pdf2image) #+BEGIN_SRC bash
source .venv/bin/activate
#+END_SRC
+ Linux : install poppler-utils Sous Windows (PowerShell) :
+ Windows : Download from: https://github.com/oschwartz10612/poppler-windows
and add it to your PATH #+BEGIN_SRC powershell
.venv\Scripts\Activate.ps1
#+END_SRC
Installer ensuite Copienator et ses dépendances Python depuis la
racine du dépôt :
#+BEGIN_SRC bash
python -m pip install --upgrade pip
python -m pip install -e .
#+END_SRC
Cette commande installe les modules contrôlés par le diagnostic du GUI
(NumPy, pandas, Matplotlib, Pillow, pydantic, pypdf, pdf2image,
ReportLab, img2pdf, PyMuPDF, ftfy, ezodf et Google Gen AI), ainsi que
les exécutables =copienator= et =copienator-gui=. Le paquet fournissant
=google.genai= est =google-genai=, et non =google=.
Les commandes restent également accessibles avec
=python -m copienator= depuis la racine du dépôt.
*** Programmes externes obligatoires
Le diagnostic vérifie que Poppler et LaTeX sont accessibles depuis
=PATH=.
**** Linux (Debian et Ubuntu)
#+BEGIN_SRC bash
sudo apt install poppler-utils python3-tk texlive-latex-extra texlive-fonts-extra lmodern python3-pygments
#+END_SRC
Cette sélection couvre les modèles LaTeX de la configuration par
défaut, notamment =standalone=, =lmodern=, =mathabx= et =minted=. Selon
les commandes LaTeX utilisées dans vos propres énoncés, d'autres
paquets TeX peuvent être nécessaires. =python3-tk= fournit Tkinter sur
les installations Python qui ne l'incluent pas d'origine.
**** Windows
1. Télécharger [[https://github.com/oschwartz10612/poppler-windows][Poppler pour Windows]] et ajouter son dossier =bin= à
=PATH=.
2. Installer une distribution LaTeX, par exemple MiKTeX ou TeX Live,
et vérifier que =pdflatex= est accessible depuis =PATH=.
Fermer puis rouvrir le terminal et le GUI après une modification de
=PATH=.
*** Programme externe facultatif
PDF Arranger permet d'ouvrir et de réorganiser plus facilement les
PDF. Son absence est signalée comme facultative dans le diagnostic. Il
doit fournir la commande =pdf-arranger= ou =pdfarranger= dans =PATH=.
*** Accès à Gemini *** Accès à Gemini
+29 -5
View File
@@ -37,6 +37,19 @@ def page_number(b, nb_pages):
center_x = (b[1] + b[3]) // 2 center_x = (b[1] + b[3]) // 2
return center_x // column_width return center_x // column_width
def sort_bounding_boxes(bounding_boxes, nb_pages):
"""Return label boxes in reading order: columns first, then top to bottom."""
return sorted(
bounding_boxes,
key=lambda entry: (
page_number(entry["box_2d"], nb_pages),
entry["box_2d"][0],
entry["box_2d"][1],
),
)
def convert_box2d(b, pn_ori, npn, tot_ori, tot_dest): def convert_box2d(b, pn_ori, npn, tot_ori, tot_dest):
l = b.copy() l = b.copy()
l[1] = (l[1] - (1000 // tot_ori) * (pn_ori-1)) * tot_ori // tot_dest\ l[1] = (l[1] - (1000 // tot_ori) * (pn_ori-1)) * tot_ori // tot_dest\
@@ -75,9 +88,8 @@ def prepare_image(image_path: str, bounding_boxes, all_labels, nb_pages, last_la
new_im = Image.new(im.mode, (width + padding, height), "white") new_im = Image.new(im.mode, (width + padding, height), "white")
new_im.paste(im, (0, 0)) new_im.paste(im, (0, 0))
draw = ImageDraw.Draw(new_im) draw = ImageDraw.Draw(new_im)
bounding_boxes.sort(key=lambda b: (page_number(b["box_2d"], nb_pages), b["box_2d"][0]))
for bbox in bounding_boxes: for bbox in sort_bounding_boxes(bounding_boxes, nb_pages):
raw_y_min = int(bbox["box_2d"][0] * height / 1000) raw_y_min = int(bbox["box_2d"][0] * height / 1000)
raw_x_min = int(bbox["box_2d"][1] * width / 1000) raw_x_min = int(bbox["box_2d"][1] * width / 1000)
raw_y_max = int(bbox["box_2d"][2] * height / 1000) raw_y_max = int(bbox["box_2d"][2] * height / 1000)
@@ -321,15 +333,28 @@ class ImageViewer:
try: try:
current_data = read_json(self.current_json_path) current_data = read_json(self.current_json_path)
nb_pages = self.current_meta["schema"]["columns_per_file"][
self.current_meta["part"] - 1
]
original_items = current_data["list"]
ordered_items = sort_bounding_boxes(original_items, nb_pages)
if ordered_items != original_items:
current_data["list"] = ordered_items
atomic_write_json(self.current_json_path, current_data)
print(
f"Reordered labels by column in "
f"{self.current_json_path.name}."
)
# Perform the conversion now, post-edit # Perform the conversion now, post-edit
converted_items = convert_list( converted_items = convert_list(
current_data["list"], ordered_items,
self.current_meta["part"], self.current_meta["part"],
self.current_meta["schema"] self.current_meta["schema"]
) )
labels = normalized_labels(current_data["list"]) labels = normalized_labels(ordered_items)
false_labels = [ false_labels = [
label for label in labels if label not in self.valid_labels label for label in labels if label not in self.valid_labels
] ]
@@ -472,4 +497,3 @@ def main(argv: Sequence[str] | None = None) -> int:
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())
+15
View File
@@ -7,6 +7,21 @@ name = "copienator"
version = "0.1.0" version = "0.1.0"
description = "Workflow assistant for preparing and correcting scanned exams" description = "Workflow assistant for preparing and correcting scanned exams"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [
"numpy",
"pandas",
"matplotlib",
"Pillow",
"pydantic",
"pypdf",
"pdf2image",
"reportlab",
"img2pdf",
"PyMuPDF",
"ftfy",
"ezodf",
"google-genai",
]
[project.scripts] [project.scripts]
copienator = "copienator.dispatcher:main" copienator = "copienator.dispatcher:main"