Chart and Imagery Collectors — Cropping What Text Cannot Carry
Two collectors run over transcribed slides and isolate the parts a reader has to see: charts with their axes and legends intact, and any visual that communicates a concept. Decoration is named and skipped. A deck-level index makes the result reusable without opening a single slide document.
new(slides): visual_collector — chart and imagery collectors over transcribed slides
new(slides): visuals.yaml — deck-level index of every collected asset
new(slides): only_slides — re-examine named slides without re-running a deck
fix(slides): repair_json — verbatim quotes no longer invalidate a response
fix(cdn): accept an explicit image name for callers whose unit is not a slide
doc(issues): crop-box under-enclosure filed as deferred, with measurements
Two collectors, one detection pass. "What visual elements are on this slide and
what is each" is one question; asking it twice costs twice and lets two passes
draw different boxes around the same region.
They fail in opposite directions, so they are tuned oppositely. A chart fails by
cropping too TIGHT — a plot severed from its axis titles, its legend, and its
units looks complete and is unreadable. So chart crops are padded outward, the
model is asked whether the key fits inside the box it drew, and a "no" doubles
the padding. Imagery fails by collecting too MUCH — decoration buries signal —
so the exclusions are enumerated rather than left to judgment, and the count of
skipped decoration is recorded per slide. Without that count, "no imagery here"
and "nothing but decoration here" are indistinguishable afterwards.
Both read the rendered slide images the stenographer already produced, so a
collection pass costs one model call per slide and no page rasterization.
**What imagery means took three corrections, all against an operator's read of a
real deck.** The first definition was pictures — diagrams, illustrations,
screenshots, photos, maps — and it collected nothing on 22 of 38 slides. Every
roadmap and every strategy framework was invisible, because a diagram built from
boxes, arrows, and text reads to a detector as formatted text. The working test
is now: *if a reader would understand the idea faster by looking at the
arrangement than by reading the words in it, it is imagery.*
The second correction was granularity, and the first attempt at it made things
worse. A roadmap with three tracks was being fragmented into its three icons, so
icons were declared decoration — which killed the fragments and left nothing in
their place. Fragmentation was the symptom; granularity was the disease. The rule
now leads with "is this slide essentially ONE visual?" and says a full-slide box
is the correct answer for a full-slide diagram. Icons came back as collectible
when standalone, and stay inside the parent's box when they are part of one.
The third was the chart/imagery boundary. Tables and bespoke plots were falling
through to imagery. The test is now explicit: if the visual contains numbers, or
encodes a magnitude, quantity, ranking, or comparison of values, it is a chart —
however unlike a standard chart type it looks.
**A guard was rejecting the right answer.** Crops were capped at 92% of the slide
to catch a detector that had boxed everything and isolated nothing. But a slide
that IS one full-bleed diagram legitimately covers the whole frame, and three
such slides were rejected as failures. The box alone cannot tell the two cases
apart; the region count can. One region covering the slide is a full-bleed
visual. One of several covering the slide is the lazy box the guard was written
for.
**Verbatim transcription and JSON are in direct tension.** A slide reading
`Jaw-Dropping ("Tell your friends" test)` is copied faithfully into a JSON string
value, and its inner quotes terminate that value early and invalidate the whole
object. Retrying is useless — at temperature zero the model reproduces the same
correct transcription and the same broken JSON. Responses are repaired instead:
inside a string, a quote ends it only when the next non-whitespace character is
structural; anything else is a quote the slide contained.
**`visuals.yaml` is what makes the output reusable.** Recording each region in
the slide document it came from is right for reading a slide and wrong for
reusing a deck — anything generating further material would have had to open
every slide document and parse frontmatter out of each. The index carries every
asset with its slide, kind, alt text, crop box, local path, CDN URL, and — for
charts — axes, legend, series, and a plain-language reading. It is regenerated
from the slide documents rather than accumulated, so it cannot drift.
**Also included:** `only_slides` re-examines named slides while leaving the rest
untouched. Detection rules change as often as they are corrected, and a rule
change is not additive — it alters how every slide would be read, so resume is a
no-op after one. This narrows a correction to the slides whose answers are known,
so it can be judged for four calls instead of thirty-eight.
**Known limitation, filed and deferred:** bounding boxes around large composite
diagrams sometimes stop short of the visual's true edges. Three rounds of
increasingly explicit instruction did not move it; a vision model estimates
regions rather than measuring them. Deferred because nothing is lost — the full
slide image sits beside every crop and the box is recorded, so re-cropping later
is a local operation with no model call and no transcription at risk. See
`context-v/issue-resolution/Visual-Collector-Crop-Boxes-Under-Enclose-Large-Diagrams.md`.What a collected asset looks like
Recorded in the slide document it came from, and again in the deck index:
charts:
- id: 09-chart-01
status: collected
kind: line
alt: A line chart comparing compute cost against model size, showing one
architecture rising exponentially while the other stays nearly flat.
crop: [0.56, 0.0, 1.0, 1.0]
title: 'Comparing Architectures: Building a Superior Approach'
x_axis: AI Model Size
y_axis: Compute Cost
legend: [Approach A, Approach B]
series: [Approach A, Approach B]
reads: As model size increases, cost for Approach A rises exponentially while
Approach B remains relatively flat.
local: ./charts/09-chart-01.jpg
cdn: https://ik.imagekit.io/<id>/<firm>/portfolio/<company>/<deck>/visuals/...A region that could not be isolated is kept, with the reason, and marked so it cannot be mistaken for an asset:
imagery:
- id: 31-imagery-01
status: rejected
kind: diagram
crop: [0.0, 0.0, 1.0, 1.0]
notes: ['region covers 100% of the slide alongside 2 other(s) — nothing was isolated']Result on the first deck
38 slides, one detection call each. 2 charts and 32 imagery collected, 43 decorative visuals deliberately skipped, all 34 assets cropped and mirrored to the CDN. By kind: framework 11, fact_callout 8, icon 5, roadmap 3, comparison_figure 2, diagram 2, line 1, table 1, timeline 1.
Measured against an independent read of eight slides whose contents were known: five exactly right, two correctly identified but under-cropped, one routed to the wrong collector. Every failure is recoverable from data already on disk.
