ImageBuffer
aglaia/ImageBuffer.py:ImageBuffer — the canonical envelope passed between every part of the system.
Fields
| Field | Type | Purpose |
|---|---|---|
buffer | np.ndarray | Pixel data. RGB (H,W,3), Gray (H,W), or BW (H,W with values in {0,255}). |
type | ImageType | COLOR, GRAY, or BW. Determines write format (jpg/png) and helper conversion. |
dpi | float | Image DPI. Used by DPIfixer, Binarizer template eval, page margin calc, dewarp. |
path | str? | Source file path (raw image) — used for routing GUI events. |
parent | ImageBuffer? | Parent buffer (set on child crops by PageDetector). |
parent_stem | str? | Parent filestem cached as string so child copies don’t drag the whole parent through pickling. |
filestem | str? | Output filename stem (no extension). E.g. mybook_001, mybook_001_A. |
out_dir | str? | Disk-write hint for ImageBuffer.write (see below). The live chain persists to the project DB and ignores it. |
scan_id, parent_node_id, pipeline_version_id, branch_path, branch_label, depth | int?/str | DB tree context — carried by the chain so Persister can link each step’s node to its scan, parent node and branch. |
children | list[ImageBuffer] | Set by branching processors (PageDetector). |
meta | dict | Per-buffer metadata. See below. |
meta keys — the schema
Every key has a declared kind (aglaia/meta_schema.py), and the kind decides what a warp does with it.
Geometric kinds are moved by the transform machinery; records stay on the node that produced them;
everything else is copied. An undeclared key is dropped at the first warp, with a log line — an
undeclared coordinate list passing through untransformed is exactly the DPIfixer bug (155ab37), so
“pass it along, it’s probably fine” is the failure mode, not the safe default.
Plugins declare theirs at import: declare_meta("stamps_found", MetaKind.SCALAR) from aglaia.plugin_api.
Keys starting with _ are a processor’s private scratch and are OPAQUE by rule.
tests/test_meta_schema.py scans the source for every meta["…"] literal and fails on an undeclared one,
and checks this table lists every declared key — neither the code nor this page can drift from the schema.
| Key | Kind | Set by |
|---|---|---|
roi | polygon — transformed by every warp | PageDetector (child outline); every COORDINATE step moves it |
erase | polygons — transformed | any erase producer (StampRemover); moved by every COORDINATE step |
parent_crop_xywh | rect [x,y,w,h] — transformed through an affine, dropped through a dewarp | PageDetector |
column_quad | geometric record in the producer’s own input frame — kept for its debug view, never carried downstream | TrapezoidalCorrection |
line_boxes | geometric record in the producer’s own input frame — kept for its debug view, never carried downstream | TrapezoidalCorrection |
H | geometric record in the producer’s own input frame — kept for its debug view, never carried downstream | TrapezoidalCorrection |
page_nums | geometric record in the producer’s own input frame — kept for its debug view, never carried downstream | (debug overlay) |
page_side | label — copied | PageDetector |
erase_sources | label — copied | erase.py |
manual | label — copied | manual.py (which fields were hand-edited) |
manual_dropped | label — copied | manual.py |
replay_kind | label — copied | every COORDINATE / PIXEL_VALUE step |
fallback_reason | label — copied | PageDewarper |
column_edge_source | label — copied | TrapezoidalCorrection |
line_source | label — copied | TrapezoidalCorrection |
mode_used | label — copied | TrapezoidalCorrection |
skew_angle | scalar — copied | SkewFinder |
skew | scalar — copied | SkewFinder (the GUI’s name, written directly) |
success | scalar — copied | PageDewarper |
oob | scalar — copied | PageDewarper (out-of-bounds fraction) |
char_h_frac | scalar — copied | TrapezoidalCorrection, PageDewarper |
recovered_aspect_w_h | scalar — copied | TrapezoidalCorrection |
dewarp_success | scalar — copied | PageDewarper |
trapezoid_success | scalar — copied | TrapezoidalCorrection |
oob_forced | scalar — copied | PageDewarper |
oob_pct | scalar — copied | PageDewarper / manual quad path |
status | scalar — copied | chain / PageDewarper |
elapsed_ms | scalar — copied | chain |
disabled | scalar — copied | chain |
gpu | scalar — copied | chain |
n_baselines | scalar — copied | TrapezoidalCorrection |
n_full_width | scalar — copied | TrapezoidalCorrection |
n_vblocks | scalar — copied | TrapezoidalCorrection |
n_vp_inliers | scalar — copied | TrapezoidalCorrection |
vp_inlier_frac | scalar — copied | TrapezoidalCorrection |
recovered_focal_px | scalar — copied | TrapezoidalCorrection |
stamps_found | scalar — copied | StampRemover (plugin) |
replay_params | opaque — copied, never interpreted in transit | every replayable step — the replay engine owns its shape |
oob_stats | opaque — copied, never interpreted in transit | PageDewarper |
manual_overrides | opaque — copied, never interpreted in transit | chain (this branch’s hand edits) |
manual_overrides_all | opaque — copied, never interpreted in transit | chain (every branch’s) |
frame_wh | opaque — copied, never interpreted in transit | debug renderers |
erase_frame_wh | opaque — copied, never interpreted in transit | debug viewer (erase edits) |
layouts_frame_wh | opaque — copied, never interpreted in transit | debug viewer (layout edits) |
Undeclared/private in the wild: _dewarp_* (PageDewarper solver state, OPAQUE by the _ rule).
When emitting image_event, _emit_event normalizes some keys for the GUI:
dewarp_success→successoob_stats→oobskew_angle→skew
Helpers
| Method | Behavior |
|---|---|
to_rgb() | Returns 3-channel RGB ndarray (converts gray/RGBA). Non-mutating. |
to_gray() | Returns single-channel grayscale. Non-mutating. |
to_bw() | Returns Otsu-thresholded BW. Non-mutating. |
check_binary() | True if 2D with ≤2 unique values. |
rescale(target_dpi, threshold) | Resizes buffer to match target_dpi (skip if diff < threshold). Mutates. |
copy() | Deep clone of buffer + deepcopy of meta. Does NOT deep-copy parent (keeps reference / parent_stem string). |
deskew(...) | Estimate + apply skew in place. Prefer the SkewFinder processor. |
binarize(), detectLayouts(), dewarp() are duck-typing convenience helpers.
write(collection, options, suffix=None, …)
ImageBuffer keeps a general-purpose disk writer (aglaia/ImageBuffer.py). It is not called by the live IntegratedProcessingChain — that path persists each step to the project .agl SQLite DB via aglaia/storage/persister.py. The logic below documents the envelope’s own write behaviour, used by off-chain helpers.
Decides where to save based on this priority:
options["paths"][collection]if set.options["paths"]["root"]if set →<root>/<collection>/<stem>.{ext}.- Existing
self.out_dir(PDF mode), with sibling-folder heuristics:- If
out_direquals the output root, redirect to its parent + collection. - If
collection == "output", respectout_direxactly. - Otherwise rewrite to
<out_dir.parent>/<collection>.
- If
- Fallback:
paths["output"].parent / collection.
Filename:
self.filestem(orpaths["filestem"], or"capture") + optional_<suffix>.- Extension:
.pngiftype == BW, else.jpg.
Before writing, same-stem conflicts in the target folder are unlinked (jpg ↔ png hygiene).
Output format:
.jpg— PIL save, RGB or L mode, quality=95, optimize=true, withdpi=(dpi, dpi)EXIF..png— BW via PIL'1'mode (1bpp), or OpenCVcv2.imwritefor color.
executor: optional ThreadPoolExecutor for async writes — only relevant to off-chain write callers; the live chain does not use this method.
After a successful write, an image_event tuple is pushed on log_queue if event_type is set:
('image_event', raw_path, # absolute normalized path of the original raw file (walked via parent chain) filestem, event_type, # usually the chain step's instance_name full_path, # absolute normalized path of just-written file gui_meta, # meta dict with key normalization parent_stem)Lifecycle examples
Capture (single page)
raw_frame (cv2 BGR) → cv2.cvtColor BGR→RGB → ImageBuffer(buf=rgb, type=COLOR, dpi=capture_dpi, filestem='mybook_001', scan_id=...) → input queue.put(...) → worker runs full pipeline in place; persist_step → DB node+image per stepPDF (2-page spread)
PDF page → PIL Image (extracted or rendered at median DPI) → raw stored as a scan in the project DB → ImageBuffer(COLOR, dpi, filestem=stem, scan_id=...) → input queue.put(ib) → worker pipeline: DPIfixer → SkewFinder → PageDetector PageDetector emits buf.children = [child_A, child_B] chain persists the parent node (marks it a branch point), persists each child node, re-injects each as a DB ref at step 4 → each child: DPIfixer (300) → Binarizer → SkewFinder → PageDewarper → each step persisted as a DB node+image (branch labels A, B)Pitfalls
- Always
copy()before crossing process boundaries. Copy a buffer before persisting or re-injecting it to avoid mutating the in-flight buffer. - BW palette preservation: after a non-binarizing step runs on a BW input, the chain re-binarizes the result with
binarize_fixed(127)so jpg compression artifacts don’t accumulate. roilives in parent coordinates until PageDetector remaps it viacv2.intersectConvexConvex(then it’s in child coordinates).dpiis metadata only — the buffer is not automatically rescaled when you set it. UseDPIfixer(orrescale()).