Skip to content

Pair a picture with the judge's by its object number - #28

Merged
tannevaled merged 1 commit into
mainfrom
match-by-object
Sep 7, 2026
Merged

tannevaled merged 1 commit into
mainfrom
match-by-object

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Refs #13.

images.match paired our picture with the first unclaimed picture of theirs of the same size. Its own comment said that was "right as often as it is wrong and is reported either way" — and it was not reported either way.

Page 1 of cerfa_10074.pdf draws 211 distinct 2×2 pictures whose stream bytes are all 00 00: uniform swatches, all ink or all paper, stretched under an /SMask that carries the glyph shapes. Two orders over 211 same-size swatches pair a black one with a white one, difference reads 1.0, and Judge writes Inverted.

The object number is the one identity both sides publish — theirs in the object column of pdfimages -list, ours by resolving the resource name render.Images hands back, through the same resource graph calibratedNames already walks.

Measured, paired: same corpus, same command, one variable

fr-cerfa page 1, (samples), 3404 pictures:

size and order object identity
direct — exact 186 of 418 418 of 418
direct — differing 232 0
converted — exact 1093 of 2949 2804 of 2949
converted — inverted 68 0
converted — differing 1788 145

And the worst of DCTDecode's converted bucket, which was a maximal disagreement:

before after
peak 255 levels 43
MSE 31 435.6 118.4

A picture was being compared against one it had nothing to do with. The 68 inversions were the pairing. The 15 that survive elsewhere are the stencil-polarity convention — which #13 says should survive, and does.

Three things identity does not settle by itself

  • A picture and its soft mask are listed under the same object number, so the row's type has to agree as well: ours is a mask or it is not.
  • A name that reaches two different objects is dropped rather than guessed at. A name is unique within one resource dictionary and not across the several a page reaches through its forms. A wrong identity is worse than none.
  • A poppler listing no object column, an inline picture with no object number, and a name nobody listed all fall back to the size rule, which is where they were before. Result.PairedBy now records which was used, so a run leaning on size is visible rather than assumed.

This does not close #13: its parts 2 and 3 — what pdfimages does with an /Indexed palette that is not exactly black, and the us-opm stencil polarity — are separate findings about the judge, not about the matcher.

100% statement coverage, race clean, nine cross-build targets, Go 1.27.1.

🤖 Generated with Claude Code

Refs #13.

The matcher paired our picture with the first unclaimed picture of THEIRS of
the same size. Its own comment said that was "right as often as it is wrong and
is reported either way" -- and it was not reported either way.

Page 1 of cerfa_10074.pdf draws 211 distinct 2x2 pictures whose stream bytes
are all "00 00": uniform swatches, all ink or all paper, stretched under an
/SMask that carries the glyph shapes. Two orders over 211 same-size swatches
pair a black one with a white one, difference reads 1.0, and Judge writes
Inverted.

The object number is the one identity BOTH sides publish -- theirs in the
object column of pdfimages -list, ours by resolving the resource name
render.Images hands back through the same resource graph calibratedNames
walks. Paired on that, over fr-cerfa page 1:

	(samples), 3404 pictures     size and order      object identity
	  direct   exact                186 of 418           418 of 418
	           differing                   232                    0
	  converted exact             1093 of 2949         2804 of 2949
	           INVERTED                     68                    0
	           differing                  1788                  145

	DCTDecode, worst of the converted bucket
	  peak                                  255                   43
	  mse                              31435.6                118.4

The 68 inversions were the pairing. The 15 that survive elsewhere are the
stencil-polarity convention, which is what #13 said should survive and does.

A converted bucket whose worst error was 255 levels and 31 435 MSE -- a
maximal disagreement -- was a picture compared against one it had nothing to
do with.

Three things the identity does not settle by itself, all conservative:

A picture and its soft mask are listed under the SAME object number, so the
row's type has to agree as well: ours is a mask or it is not.

A name that reaches two different objects is dropped rather than guessed at. A
name is unique within one resource dictionary and not across the several a page
reaches through its forms, so a page whose two forms each name their own Im1
has one ambiguous name and everything else still paired by identity. A wrong
identity is worse than none.

A poppler that lists no object column, an inline picture with no object number,
and a name nobody listed all fall back to the size rule, which is where they
were before. Result.PairedBy now records which was used, so a run leaning on
size is visible rather than assumed.

100% statement coverage, race clean, nine cross-build targets, Go 1.27.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 384dca9 into main Sep 7, 2026
1 check passed
@tannevaled
tannevaled deleted the match-by-object branch September 7, 2026 15:36
tannevaled added a commit that referenced this pull request Sep 7, 2026
The records described a library measured through an instrument that has since
been replaced. #28 stopped pairing a picture with the first unclaimed picture
of poppler's OF THE SAME SIZE and began pairing by object number, so every
`inverted` and `differing` figure taken before it was partly an artefact of how
the two sides were lined up.

Everything else was held, and checked BEFORE the run rather than asserted after
it: the same judge (pdfimages 26.04.0), the same five module versions, one page
per document, the same two-minute bound. The measure is the only thing that
moved, which is what this file demands of any figure it prints.

Over all 23 populations and the same 7515 compared pictures:

	                      2026-08-31   2026-09-07
	pictures compared           7515         7515
	exact                       4611         6598
	reported inverted            488          422
	reported differing          2416          495
	agreement                  65.6%        93.0%

Six populations moved; seventeen are identical to the byte. The picture count is
unchanged, so none of it comes from measuring less.

It is not uniformly "fewer inversions". ia-uscourts GAINED two, 37 to 39, while
its differing count fell by the same two: the right pairing found two genuine
complements the wrong one had hidden inside a disagreement. A change that only
ever removed inversions would be a change that only ever flattered.

Section 8 said "Inversions are unchanged, and still conventions". This run
disproves it, so it is rewritten rather than left standing -- with what it got
right kept, since the complements on the mask filters ARE the convention and
did not move.

Section 9 is new: with the pairing fixed, every lossless path is at or near the
top -- JPXDecode 100.0%, (samples) 97.4%, the mask filters 96.3% and 100.0% --
and DCTDecode sits at 43.7%, having moved 0.5 points because the pairing was
never its problem. That is the shape of the remaining work, and the old numbers
hid it: at 69.7% against 43.2% the two looked like the same kind of problem at
different depths, and only one of them is a codec difference worth a library
change.

Sections 1-7 are about the earlier comparison and are kept as that, with a note
at the head of the analysis saying so. Section 7's own table is refreshed and
its claim re-checked: every agreeing picture of every lossless filter is still
bit-equal.

Every table here was regenerated by a script proved against the PREVIOUS
README first -- all 31 population rows, all 8 filter rows and all 39 bucket
rows reproduced verbatim from the old records before it was pointed at the new
ones. A first draft of it silently dropped the two populations that draw no
pictures; that is why the proof is run in that direction.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
tannevaled added a commit that referenced this pull request Sep 7, 2026
)

The fallback exists for when the object number cannot answer: an older
poppler with no object column, or a picture whose object this side could
not find. It was also running when BOTH sides published a number and the
two simply differed -- which does not mean "look harder", it means the
judge did not extract this picture.

Taking a row anyway costs twice. 2044_2044_4764.pdf holds ten 118x118
Data Matrix barcodes in one shared resource dictionary; a barcode the
page never draws was paired with the row belonging to the one it does,
reported 255 apart on every term, and the barcode that WAS drawn came
back unpaired and unmeasured. Over the corpus, 30 of the 41 structural
JPEG disagreements were pairs like that.

The stand-in judge wrote object 7 on every listing row, and 7 is not an
object any of these fixtures holds. So every end-to-end test here was
pairing by SIZE, and the by-object path added in #28 was reached by the
unit tests alone -- the fixture agreed with nothing, which is why it
never contradicted anything. It now reports the object numbers the file
actually holds, read by scanning its bytes rather than by asking this
package, and the end-to-end tests pair by object as the corpus does.
standInWithoutObjects keeps the older-poppler case honest and explicit.

Making the stand-in faithful uncovered a silent refusal: a pair whose
two sides come out different sizes has no pixel to set against a pixel,
and came back with Share -1 and no note -- which reads exactly like an
agreement of zero. It now says both sizes.

Co-authored-by: Claude Opus 5 <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.

The concentrated inversions are pdfimages, not our decoding — and 144 of the 173 are this repository's size matcher

1 participant