Skip to content

Report rendering: serology halo + native multiple_circles + fully-vector single-canvas signature page - #34

Open
drserajames wants to merge 6 commits into
mainfrom
sigp-vector
Open

Report rendering: serology halo + native multiple_circles + fully-vector single-canvas signature page#34
drserajames wants to merge 6 commits into
mainfrom
sigp-vector

Conversation

@drserajames

Copy link
Copy Markdown
Owner

Report-rendering follow-ups: serology halo, multiple_circles native, and a fully-vector single-canvas signature page

Bundles three independent follow-ups on the now-default native map renderer. All build together; the standalone map-draw/tal-draw/geo-draw file outputs are verified pixel-identical to main (content AE=0), so nothing regresses the merged native default.

1. Serology label-halo miter join (cc/draw/cairo-surface.cc)

Point-label halos used a round line join; kateri uses the PDF-default miter join. Switched text_font's halo to miter (scoped to text_font; text()/text_rotated() unchanged). A genuine fidelity alignment; the serology-map residual itself is a font-rasterisation floor (native renders true embedded bold; the harness rasteriser degrades the golden's non-embedded bold) — not a geometry gap.

2. multiple_circles renders natively (py/ae/report/multiple_circles.py)

The multiple-serum-circles addenda no longer drive kateri: render_pdfs() writes the styled chart once and renders every style via ae_backend.map_draw.export_styled_maps. No kateri process; the translucent theoretical-circle clade fills now render (via the recent #AARRGGBB fill fix); the -names pdflatex overlay is preserved. Matches kateri at the AA floor (≈1.5–1.6%).

3. Fully-vector single-canvas signature page (cc/tal/sig-page.*, cc/map-draw/, cc/tal/draw-tree.*, cc/draw/cairo-surface.*, py/ae/tal/signature_page.py)

Signature pages composed the tree + section maps as separate PDFs stitched with pdfjam, maps rendered by kateri. This renders the tree and every section map as vectors into one shared Cairo PDF surface — no kateri, no pdfjam, no PNG tiles.

  • New borrowed-context CairoPdf constructor targets a device sub-rectangle of a caller-supplied cairo_t (translate/scale/clip; borrows, never destroys). Existing file-bound constructor untouched.
  • export_styled_map and the tal tree render factored so each has a file entry point (byte-identical output) plus an _into(context, rect) entry point.
  • SigPageCanvas (bound as ae_backend.tal.SigPageCanvas) owns one cairo_pdf_surface and draws maps + tree into their computed rects; signature_page.py defaults to this native vector path (kateri/pdfjam kept as native=False fallback).
  • Safety check: standalone map/tree/geo renders are pixel-identical pre- vs post-refactor (AE=0/0/0). The vector sig-page composes correctly (real 8-section page, one canvas), registering crisply vs the pdfjam baseline within the native↔kateri map AA floor.

Notes

  • WHO-data gate clean on the whole tree and all commit messages; charts referenced read-only from outside the repo; no strain/clade data in committed files or docs.

drserajames and others added 6 commits July 18, 2026 07:42
Switch the multiple-serum-circles addenda map render from kateri (driven
over its unix socket, one session per lab) to the native headless renderer,
mirroring ae.report.map_renderer.NativeRenderer. render_pdfs now writes the
in-memory styled chart to a short-lived temp .ace and renders each named
style from it in one ae_backend.map_draw.export_styled_maps call (chart
loaded once) — no kateri process, no socket, Linux-capable.

Each style resolves its own on-chart c["R"] named style + c["p"] base
plot-spec, including the per-lab square viewport set by build_styles,
exactly as kateri's set_style + get_pdf(square=True) did. The recent
map-draw serum-circle fill fix means the theoretical circles' translucent
(#AARRGGBB) clade-colour fills now render natively.

The -names pdflatex overlay step is unchanged (it runs on top of the
natively-rendered circles map). Removed the now-dead asyncio import, the
ae.utils.kateri dependency and the _kateri_app_bundle helper.

Verified on h3-hint-cdc: the three PDFs render with no kateri process, and
native vs a same-config kateri render matches at the anti-aliasing floor
(~1.5% of pixels at fuzz 30%, dominated by grid-line sub-pixel offset,
point-edge AA and title-font differences; map content and translucent
circle fills match).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The native renderer stroked point-label text halos with a round line join,
but kateri (draw_on_pdf.dart) leaves the PDF-default MITER join in place for
its halo stroke pass. Round bevels glyph terminals and under-covers the halo
band relative to the golden. Switch text_font's halo stroke to MITER so the
label halo matches kateri exactly.

Scope limited to text_font (the semantic point-label path); text() and
text_rotated() keep their round join to avoid untested impact on the
tal-draw / geo-draw halos that share those primitives.

Verified against the p2-fidelity harness (serology + by-clade maps): the
by-clade fuzz30 residuals are unchanged (no regression); serology strict AA
counts drop marginally. The remaining serology-vs-golden residual is a font
rendering floor (see investigation notes), not a geometry gap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eri)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Draw the signature page's tree (left) and per-section antigenic maps (right)
as VECTORS onto ONE shared cairo_pdf_surface — no PNG tiles, no kateri, no
pdfjam/pdflatex.

Shared-context refactor (transparent to the existing file-output path):
- cc/draw/cairo-surface.{hh,cc}: add a borrowed-context CairoPdf constructor
  that targets a device sub-rectangle of a caller-supplied cairo_t (translate
  +scale+clip); the destructor only restores, never destroys. File-bound
  constructor unchanged.
- cc/map-draw/styled-draw.cc: factor the per-map compute+draw behind a surface
  factory; export_styled_map(..., output) keeps byte-identical file output,
  new export_styled_map_into(..., context, rect) letterboxes the map into a
  shared sub-rect.
- cc/tal/draw-tree.cc: same factoring; export_tree_pdf (file, unchanged) +
  export_tree_into (shared sub-rect).

Compositor + wiring:
- cc/tal/sig-page.{hh,cc}: SigPageCanvas owns one PDF page and renders the
  section maps (chart loaded once) + tree into their computed rects as vectors.
- cc/py/sig-page.cc: ae_backend.tal.SigPageCanvas binding (registered in
  module.{cc,hh}); meson links the tal tree renderer + geo continent sources
  into ae_backend so the binding can reach both renderers.
- py/ae/tal/signature_page.py: make_section_signature_page_native drives one
  SigPageCanvas from the ported auto-width geometry; make_section_signature_page
  now defaults to this native vector path (native=False = legacy kateri/pdfjam).

Verified: standalone export_styled_map / tal-draw / geo-draw file output is
pixel-identical to pre-refactor (AE=0 each); a real ~8-section page composes on
one canvas, page size within ~2% of the pdfjam baseline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The fully-vector signature-page compositor drifted vs the report's signature
pages in text size/weight and page size because two driver-side geometry rules
did not reproduce the LaTeX compose_grid (keepaspectratio) path they port.

1. Tree render size. make_section_signature_page_native rendered the tree at the
   panel height in points (round(th)) and placed it 1:1. The tree renderer clamps
   fonts/line-widths to ABSOLUTE device bounds, so it does NOT scale linearly with
   image_size: rendering at the small panel height makes those clamped glyphs/lines
   proportionally larger (bolder tree, drifted label spacing) than the baseline,
   which renders at 1000 and optically scales down. Render at the same internal
   size the baseline uses (size or tal_size or 1000) and let export_tree_into
   letterbox-scale it (vector, lossless); the tree page aspect equals the panel
   aspect, so it still fills the panel exactly.

2. Page size. _sig_page_layout used exact fractional mm while compose_grid rounds
   the LaTeX paper to whole mm ({:.0f}mm). Round the composed page to whole mm so
   the page aspect — hence the scale pdfpages applies embedding the sig page onto
   the report's A4 pages — matches the baseline and text lands at the same absolute
   size in the assembled report.

Both changes are confined to the Python driver; no cc/ file is touched, so the
standalone export_styled_map / tal-draw / geo-draw file renderers are byte-identical
(AE=0 by construction; module not rebuilt). Verified on bvic-niid (8 sections):
composed page 926.929x572.598 pt = the live compose_grid path exactly; aa-label
glyph height 5.23 pt vs 5.22 pt baseline (0.2%); tree span within 0.2% of the live
LaTeX fit. Doc updated (SIG-PAGE-COMPOSITOR.md §5/§6) incl. the residual uniform
~10 pt minipage[t]/topskip offset (shifts tree+maps together, re-centred by pdfpages
in the A4 report) and the stale-baseline note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant