19 KiB
Final output: A Rendre
This documents the current implementation, as of 2026-09-12. The return folder
referred to as « À rendre » is named A Rendre on disk. JPEG files use the
extension .jpg, not .jpeg.
Left space for Gemini comments
config.py (or the file selected by COPIENATOR_CONFIG) can override:
ANNOTATION_MARGIN_LEFT = 300
ANNOTATION_MARGIN_POINTS = [(10, 450), (20, 400), (35, 300)]
The reference points are (measured margin in mm, added pixels at 200 dpi).
Each page fragment gets its own spacing: 450 px at or below 10 mm, 400 px at
20 mm, and 300 px at or above 35 mm, with linear interpolation between points.
Unknown pages retain their unknown status but use the arithmetic mean of the
copy's measured page margins, including during label-strip positioning.
Inferred values never contribute to that mean. If no page has a measurement,
ANNOTATION_MARGIN_LEFT supplies the default 300 px.
The old ANNOTATION_LOW_MARGIN_LEFT setting is replaced by the reference points.
The 600 px comment width is unchanged, so
comments can still overlap handwriting on pages with very little whitespace.
Automatic crop-labels saves per-page measurements in the Cutleft schema.
split-answers records source pages, crop rectangles and remaining margins in
*.layout.json files beside answer PDFs. Those files follow copies, manual
merges and vertical cuts. PDF fingerprints prevent using stale measurements
after files are replaced. Legacy schemas and manual/full-page Cutleft runs
without measurements keep the normal spacing.
For an existing evaluation, rerun automatic crop-labels, split-answers, and
annotation generation with overwrite to populate and use this metadata. Do
this before handwritten review: existing Bnot/BGnot/BRnot annotation batches
save their rendering geometry and reuse it when read back, even after settings
change. Older batches without saved geometry keep their original 300 px offset.
Do not overwrite a batch that still contains handwritten corrections to import.
Each fragment is positioned independently, with its annotation rectangles shifted by the same amount. Rectangles crossing page boundaries are split at those boundaries. Narrow pages receive any additional width on the right, so left padding does not stack with the configured allowance. BGnot pads narrower answer images with white without resizing handwriting; fitting the wider document to screen or paper may reduce the displayed scale, and return-PDF page breaks can change. The extra space is also used by Anot and Bnot.
Files and their sources
After grouped correction and review:
python -m copienator read-grouped Interro
python -m copienator giving-names Interro BGnot
The expected layout for a copy is:
Interro/A Rendre/
└── Student Name (01)/
├── Student Name.jpg
├── Student Name.pdf
├── score.json
├── info.json
└── answers/ # when individual answer export is enabled
├── 001 - Ex 1.jpg
└── 002 - Ex 2.jpg
The name comes from Copies/Copie01.json (name), with filename sanitization.
The copy ID distinguishes folders even when several copies have the same name.
giving-names links the full JPEG, PDF and score file (or copies them when links
are unavailable). It writes info.json and optionally composes the individual
answer JPEGs.
| Return file | Source under BGnot/Copie01/ |
Contents |
|---|---|---|
Student Name.jpg |
Concat.jpg |
Full continuous image of the compiled answers and corrections. |
Student Name.pdf |
Concat_F.pdf |
Filtered, paginated correction with context, questions and solutions. |
score.json |
score.json |
Per-question scores, including questions omitted from the filtered PDF. |
info.json |
info.json |
Answer presence, empty-answer classification, PDF membership and score. |
answers/*.jpg |
Final per-label JPEGs selected by info.json |
One annotated answer, including empty scans, with optional supplementary material. |
Enabling or disabling outputs
Set these independent options in config.py (the defaults also apply when
absent from an older personal configuration):
RETURN_JPEG_ENABLED = True
RETURN_PDF_ENABLED = True
RETURN_ANSWERS_ENABLED = False
RETURN_ANSWERS_CONTEXT = False
RETURN_ANSWERS_QUESTION = True
RETURN_ANSWERS_SOLUTION = False
The personal config.py enables RETURN_ANSWERS_ENABLED; the distributed
default is False. Set a full-output option to False, then rerun giving-names to omit that file from
A Rendre. For each prepared copy, any existing named return file of a disabled
type is removed, including a symlink or fallback copy. Its annotation source
remains intact. These options control return publication, not intermediate
rendering or scoring. score.json and info.json are always included and have
no disabling options.
Cleanup allows the JPEG to be absent when disabled, still requires score.json,
and preserves return PDFs when present.
Individual answer JPEGs
With RETURN_ANSWERS_ENABLED = True, giving-names generates an answers/
subdirectory inside each student's return folder. It includes every present
compiled answer, even a perfect answer omitted from the filtered PDF. Scans
marked empty-answer are included; labels without an answer are excluded. Every JPEG
contains the final annotated student answer, including retained feedback and
extracted handwriting.
The three supplementary options independently prepend, in this order:
- Applicable context PDFs, if
RETURN_ANSWERS_CONTEXTis enabled. - The question, if
RETURN_ANSWERS_QUESTIONis enabled (the default). - The model solution, if
RETURN_ANSWERS_SOLUTIONis enabled. - The annotated student answer, always.
These use the same Text2/Sol2 sources as the filtered PDF. Missing supplements
are skipped; an unreadable existing file fails the export. They are concatenated
vertically on white, without PDF pagination or its black/blue borders. The
options affect only these individual images, not the full JPEG or filtered PDF.
Disabling all supplements produces just the annotated answer.
Filenames use natural label order, a three-digit minimum sequence number, and a
sanitized label (001 - Ex 1.jpg). Numbering prevents filename collisions when
labels differ only by characters forbidden in filenames. JSON keys retain exact
labels. The managed answers/ directory is replaced on successful generation,
so removed answers do not leave stale images; failures preserve the previous
directory. Disabling the option clears this directory on the next giving-names
run. Separate storage keeps these images out of the personal final-mark stamping
step, which reads only JPEGs directly inside the student's folder.
Recompile annotations once before exporting old evaluations with this option:
the compiler now saves every final answer block and writes info.json
next to them. For the grouped workflow, run read-grouped, then giving-names.
This avoids reconstructing a reviewed answer from outdated correction data.
info.json: per-question information
Every label in score.json has an object containing exactly four fields:
{
"Ex 1": {"present": true, "not_empty": true, "touched": false, "score": "4"},
"Ex 2": {"present": true, "not_empty": true, "touched": true, "score": "2"},
"Empty": {"present": true, "not_empty": false, "touched": false, "score": "0"},
"Absent": {"present": false, "not_empty": false, "touched": false, "score": ""}
}
present: an answer entry exists for this student and label in the compilation data. A supplied answer judged empty still haspresent: true. A body removed by manual annotation edits haspresent: false.not_empty: the answer was not markedempty-answerand was successfully compiled. Absent answers havenot_empty: false. When individual export is enabled, everypresentanswer gets a JPEG, including those withnot_empty: false. Image retention alone does not change the empty classification. These fields describe the answer regardless of export settings.touched: the answer appears in the compiled filteredConcat_F.pdf, using the actual selection including handwriting and selective redo preservation. It is not a flag for human edits. Empty and absent answers havefalse.score: the same value asscore.json(normally a numeric string, or""for an unpopulated score). Editing scores requires recompilation to update the images; return publication uses currentscore.jsonvalues for this field.
info.json is always exported, even when individual JPEGs or the named PDF are
disabled. touched tracks BGnot filtered-PDF membership. In Anot and
Bnot flows, all touched values remain false. Bnot also produces a filtered,
paginated PDF with context, questions and solutions, alongside its filtered JPEG.
This file replaces touched.json and the internal answer_labels.json manifest.
Recompile old annotations, then run giving-names; successful regeneration and
publication remove the obsolete files from their respective folders. Missing
or malformed metadata requires recompilation rather than guessing answer presence
from scores. Cleanup preserves info.json alongside score.json.
JPEG: the full compiled correction
The JPEG stacks the rendered answer blocks vertically in natural label order (for example, Ex 2 precedes Ex 10). Each block contains the scanned answer, its label and score, retained global and local feedback, and detected handwritten review annotations. Local feedback can include red rectangles and comments in the left margin. Review checkboxes are applied as actions rather than reproduced as controls; internal error labels are hidden during recompilation.
The result is one RGB image of variable height, with no page breaks. It contains all successfully compiled answer blocks, including answers scored 4 with no remaining feedback. “Full” refers to those answer blocks, not the original scan pages or every question in the statement. Missing/unrenderable answers cannot be included. Empty-answer scans are retained individually but excluded from the combined and filtered documents. The grouped compiler refuses to publish a new set when compilation is incomplete.
The JPEG does not prepend the question, context or model solution PDFs.
PDF: a different selection and layout
The PDF is not a PDF conversion of the JPEG. During an ordinary full grouped recompilation, an answer is omitted only when all three conditions hold:
- Its score is at least 4.
- Every feedback item is marked
to_delete(also true for an empty feedback list). - There are no significant detected handwritten annotations for that answer.
Thus, a 4/4 answer with retained feedback or handwriting still appears. Scores
for omitted answers remain in score.json, and their answer blocks remain in
the JPEG. Handwriting significance currently means more than 20 pixels with
alpha greater than 50 in the extracted annotation layer.
For each retained answer, the PDF stacks the following available material:
- Applicable context PDFs from
Text2/CTXT first_label -> last_label.pdf. - The question from
Text2/<label>.pdf. - The model solution from
Sol2/<label>.pdf. - The same compiled answer block used for the JPEG.
Missing supplementary PDFs are skipped. Each distinct context appears only once per student PDF, before its first retained question. Duplicate detection uses extracted PDF text with Unicode and whitespace normalization, including contexts stored under different filenames or validity ranges. Numbers, mathematical symbols and case remain significant. Contexts without extractable text use a PDF-content hash instead, so unrelated image-only contexts are not collapsed. The same rule applies to selective redo and manual annotation modifications. Each question's complete group stays together on one page; groups are packed until the next would exceed the target height. An oversized group gets its own taller page, rather than being split. Pages are raster images saved as PDF at 100 dpi, with variable heights, not fixed A4 sheets or searchable text.
The target height is int(max_image_width * 1.414 * 1.25) pixels. Small white
margins are added on the left and above/below the page. The question receives
a black border and the model solution a blue border. Context and student answer
blocks receive neither border, including when supplementary files are missing.
If nothing survives filtering, read-grouped removes old Concat_F outputs and
does not create a new PDF. During a selective --refaire merge, saved answer
images outside the selection are kept in the filtered output without reapplying
the perfect-answer filter; the resulting PDF can therefore retain more answers
than a full recompilation.
JSON: per-question scores
score.json is a flat JSON object keyed by the exact question labels. For example
(illustrative data):
{
"Ex 1 : 1)": "4",
"Ex 1 : 2)": "2.5",
"Ex 2": ""
}
Values are strings, including numeric scores. The normal question scale is
0 to 4. "" means no score was populated for that label; it is distinct from
"0". The compiler initializes all labels from the evaluation's labels file
to "", then fills processed scores. This file contains no student identity,
feedback, annotation coordinates, grading weights, or overall final mark.
Scores incorporate review checkbox changes and, when requested, existing score
overrides via read-grouped --update-score (or read-annotations --update-score
for Bnot). Editing the JSON alone does not update the rendered scores. Overrides
are read from the annotation source folder: a return symlink points there, but
an independent fallback copy does not. Successful annotation rereads automatically
refresh individual answers/ images in existing return folders, matching their
trailing copy ID and preserving manual names and other return files. This also
applies the selected supplementary-material options. Copies whose regeneration
fails retain their existing exports. Rerun giving-names to refresh other copied
return files.
With --update-score, both readers save a small content fingerprint per copy
after successful regeneration and answer publication. Subsequent runs skip
unchanged copies before scanning or rendering PDFs. A single edited score.json
therefore rebuilds only its copy. Changed shared documents or grouped annotation
inputs conservatively invalidate all copies; changed export options also trigger
regeneration. Missing generated files and failed runs are retried. The first run
establishes the fingerprints. Reads without --update-score always regenerate;
selective --refaire reads do not use this optimization.
update-ods reads these return JSON files; empty values become NT in the normal
per-question export. Its --sum option sums numeric values and skips nonnumeric
ones. Weighting and the final overall mark belong to the separate grading flow.
Availability and later steps
giving-namesacceptsBGnot,Bnot, orAnot. It selects the requested source ifscore.jsonand eitherConcat.jpgorinfo.jsonexist, otherwise it triesAnot/CopieXX. This also permits returns for an entirely empty copy. It links the PDF only ifConcat_F.pdfexists. The normalBnotreader produces bothConcat_F.jpgandConcat_F.pdffor nonempty filtered answers. Simple annotation producesConcat.jpgwithout a filtered PDF.- Preparing returns removes disabled named outputs, and removes older named outputs whose source is now absent, including broken symlinks.
add-final-scorewrites toFINAL_SCORE_OUTPUT_DIR/<evaluation>/. It stamps the overall mark from the configured ODS in red at the JPEG's upper right, rounded down to one decimal place. It copies PDFs unchanged, together withscore.json,info.jsonand the optionalanswers/directory. Alongside the student folders it writesstat.json, averaging each label across the currentA Rendre/*/score.jsonfiles and rounding upward to one decimal place, stored as strings like the original scores (for example"2.7"). Blank and missing scores are excluded; labels without numeric scores retain"". It does not add the overall mark to the files insideA Rendre.- The
cleancommand retains return images (including individual answers), PDFs,score.jsonandinfo.json, materializing retained symlinks before deleting their sources.
Implementation references
- Naming and return links
- Individual answer publication
- Grouped compilation, filtering and PDF pagination
- Answer rendering
- Handwriting detection and per-copy compilation
- Score and feedback actions
- Context, question and solution lookup
- ODS export
- Final-mark stamping
- Cleanup retention
Manual modifications after annotation reading
modify-annotations EVALUATION --annotation-dir BGnot (or Bnot) applies
annotation_modifications.txt. The GUI exposes it between annotation reading
and return preparation. Its ### Source: BGnot/Bnot line must match the mode.
Instruction operators follow manual resolution syntax but act on reviewed raster
bodies, excluding headers and all supplementary pages. Headers and scores stay
with their labels. Missing target bodies are allowed; their new header identifies
the target and its existing score, without inventing feedback or a score.
Both readers record annotation_baseline.json and lossless hidden
.annotation-base-*.png images in each copy's annotation directory. The JSON
stores the exact header height, baseline answer metadata, and filtered membership.
Older outputs require one full annotation read before body editing. Legacy
selective redo still works, but cannot infer missing header boundaries.
Each application rebuilds from that baseline and publishes all affected copies
and the mode's annotation_modifications_applied.txt in one rollback-capable
transaction. Reapplying is idempotent; removing an instruction restores its prior
content and membership. Annotation rereads refresh the baseline and replay the
applied snapshot, not the draft. Selective redo preserves unaffected baseline
layers so previous transfers are never included twice.
The operation rebuilds per-label JPEGs, info.json, Concat.jpg, and the filtered
BGnot PDF or Bnot JPEG and PDF. Every changed non-empty body is included in the filtered
output, with its destination label's supplementary material. score.json is not
modified. The GUI marks downstream return preparation stale. Rerun giving-names
without --update to refresh both full outputs and individual answer exports.