Refuse centering measurement on die-cut/clear-window cards

Same failure family as the edge-whitening fix: centering_profile assumed a
uniform printed border, so on an E-X/SPx-style die-cut the side midpoint
samples landed on clear acetate and the border-width walk measured two
different materials, producing a confident 70/30 the model then treated as
authoritative. Now the four side samples must read as the same material or
the measurement refuses with the reason; the prompt tells the model to judge
die-cut centering as design-to-cut registration instead.
This commit is contained in:
Barely Removable 2026-08-22 11:34:36 -07:00
parent bb5d50502c
commit fa5467e55d
3 changed files with 81 additions and 6 deletions

View file

@ -619,6 +619,16 @@ def centering_profile(image_bytes):
Returns None when the card has no uniform border to measure against Returns None when the card has no uniform border to measure against
(full-bleed modern cards) or the read looks implausible, so the caller (full-bleed modern cards) or the read looks implausible, so the caller
can fall back to the model's own eye rather than trust a bad number. can fall back to the model's own eye rather than trust a bad number.
Returns {"reliable": False, "reason": ...} a refusal with an
explanation, distinct from a silent None when the four sides read as
DIFFERENT materials from each other. A die-cut card with a clear window
(SPx, E-X Century) is the case that forced this: the side midpoints land
on acetate showing the background through it, the walk inward measures
the widths of two different materials, and the result is a confident
70/30 on a card whose ratio was never measurable which the model then
treats as authoritative and caps the grade with. Same failure family as
the per-edge outlier check in edge_wear_profile, one measurement over.
""" """
if Image is None: if Image is None:
return None return None
@ -633,10 +643,51 @@ def centering_profile(image_bytes):
px = card.load() px = card.load()
# The border colour, sampled just inside the cut at the midpoint of # The border colour, sampled just inside the cut at the midpoint of
# each side — far from corners and from any design element. # each side — far from corners and from any design element. A small
# patch mean per side rather than one pixel, since a single pixel of
# noise shouldn't decide whether the whole measurement runs.
inset = max(2, int(min(w, h) * 0.012)) inset = max(2, int(min(w, h) * 0.012))
samples = [px[inset, h // 2], px[w - 1 - inset, h // 2], patch = max(2, int(min(w, h) * 0.008))
px[w // 2, inset], px[w // 2, h - 1 - inset]]
def patch_mean(cx, cy):
total = [0, 0, 0]
n = 0
for x in range(max(0, cx - patch), min(w, cx + patch + 1)):
for y in range(max(0, cy - patch), min(h, cy + patch + 1)):
p = px[x, y]
total[0] += p[0]; total[1] += p[1]; total[2] += p[2]
n += 1
return (total[0] // n, total[1] // n, total[2] // n)
side_samples = {
"left": patch_mean(inset, h // 2),
"right": patch_mean(w - 1 - inset, h // 2),
"top": patch_mean(w // 2, inset),
"bottom": patch_mean(w // 2, h - 1 - inset),
}
# All four sides must plausibly be the SAME border before a ratio of
# their widths means anything. A side whose colour sits far from the
# median of the other three is a different material — a clear die-cut
# window showing the background, a foil accent strip — and one such
# side invalidates the whole geometric premise, not just its own
# number: the walk inward on every side keys off one shared
# border_rgb. Refuse with the reason rather than return a ratio.
for side, colour in side_samples.items():
others = [v for s, v in side_samples.items() if s != side]
med = tuple(sorted(o[i] for o in others)[len(others) // 2]
for i in range(3))
if sum(abs(colour[i] - med[i]) for i in range(3)) > 150:
return {
"reliable": False,
"reason": ("the {} side's border reads as a completely "
"different colour/material from the other "
"sides — typical of a die-cut or clear-window "
"card, where a border-width ratio doesn't "
"describe centering at all").format(side),
}
samples = list(side_samples.values())
border_rgb = tuple(sorted(c[i] for c in samples)[len(samples) // 2] border_rgb = tuple(sorted(c[i] for c in samples)[len(samples) // 2]
for i in range(3)) for i in range(3))
@ -668,6 +719,7 @@ def centering_profile(image_bytes):
return None return None
return { return {
"reliable": True,
"widths_px": widths, "widths_px": widths,
"horizontal": horizontal, "horizontal": horizontal,
"vertical": vertical, "vertical": vertical,

View file

@ -137,8 +137,10 @@ function renderGradeBlock(g, opts = {}) {
} }
const cm = g.centering_measurement || null; const cm = g.centering_measurement || null;
const centeringLine = cm const centeringLine = cm && cm.reliable === false
? `measured — left/right ${cm.horizontal_label} · top/bottom ${cm.vertical_label}` : ''; ? `not measurable — ${cm.reason || "this card's cut"}`
: (cm && cm.horizontal_label
? `measured — left/right ${cm.horizontal_label} · top/bottom ${cm.vertical_label}` : '');
// Only worth surfacing when it's away from noise — most cards sit within // Only worth surfacing when it's away from noise — most cards sit within
// a percent or two of standard and repeating that number on every card // a percent or two of standard and repeating that number on every card

View file

@ -264,6 +264,14 @@ leniently, 75/25 for a 10 and 90/10 from 9 downwards):
Note that centering is a manufacturing trait, not damage: a badly centred \ Note that centering is a manufacturing trait, not damage: a badly centred \
card can still be pristine, and PSA may grade it strongly with an OC \ card can still be pristine, and PSA may grade it strongly with an OC \
(off-centre) qualifier rather than a low number. (off-centre) qualifier rather than a low number.
DIE-CUT and clear-window cards (SPx, E-X Century, jersey- or \
shape-die-cuts) have no uniform rectangular border, so border-width ratios \
mostly do not apply to them. Judge centering there as the printed design's \
registration to the die-cut shape and the card's physical edges — that is \
what PSA keys off for these issues and do not reconstruct a border ratio \
by eye on a card that has no border. If a MEASURED CENTERING block was \
supplied anyway on such a card, distrust it outright: the measurement \
assumes a uniform printed border this card does not have.
- CORNERS: look for whitening, fraying, softness, or blunting at each of the \ - CORNERS: look for whitening, fraying, softness, or blunting at each of the \
four corners. Sharp corners on all four is 9-10 territory; slight whitening \ four corners. Sharp corners on all four is 9-10 territory; slight whitening \
visible under magnification but not to the eye is 8-9; obvious whitening or \ visible under magnification but not to the eye is 8-9; obvious whitening or \
@ -646,7 +654,20 @@ def grade_card(images, api_key=None, model=None, effort=None, zoom_details=True)
aspect = cardimage.aspect_profile(images[0][0]) if can_measure else None aspect = cardimage.aspect_profile(images[0][0]) if can_measure else None
prompt_parts = [] prompt_parts = []
if centering: if centering and centering.get("reliable") is False:
prompt_parts.append(
"CENTERING COULD NOT BE MEASURED on this card, because {}. "
"You are getting no border-width numbers, and on a card like "
"this a border-width ratio is the wrong tool anyway: judge "
"centering by eye as the printed design's alignment relative to "
"the die-cut shape and the card's physical edges — that is what "
"PSA actually keys off for die-cut issues. Do not reconstruct a "
"left/right border ratio visually on a card with no uniform "
"border; describe what you can genuinely see about the cut's "
"registration to the printed pattern, and widen the range if "
"the photo doesn't settle it.".format(
centering.get("reason", "of its cut")))
elif centering:
prompt_parts.append( prompt_parts.append(
"MEASURED CENTERING — computed from the pixels by finding the " "MEASURED CENTERING — computed from the pixels by finding the "
"border on each side of the card:\n" "border on each side of the card:\n"