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:
parent
bb5d50502c
commit
fa5467e55d
3 changed files with 81 additions and 6 deletions
58
cardimage.py
58
cardimage.py
|
|
@ -619,6 +619,16 @@ def centering_profile(image_bytes):
|
|||
Returns None when the card has no uniform border to measure against
|
||||
(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.
|
||||
|
||||
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:
|
||||
return None
|
||||
|
|
@ -633,10 +643,51 @@ def centering_profile(image_bytes):
|
|||
px = card.load()
|
||||
|
||||
# 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))
|
||||
samples = [px[inset, h // 2], px[w - 1 - inset, h // 2],
|
||||
px[w // 2, inset], px[w // 2, h - 1 - inset]]
|
||||
patch = max(2, int(min(w, h) * 0.008))
|
||||
|
||||
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]
|
||||
for i in range(3))
|
||||
|
||||
|
|
@ -668,6 +719,7 @@ def centering_profile(image_bytes):
|
|||
return None
|
||||
|
||||
return {
|
||||
"reliable": True,
|
||||
"widths_px": widths,
|
||||
"horizontal": horizontal,
|
||||
"vertical": vertical,
|
||||
|
|
|
|||
|
|
@ -137,8 +137,10 @@ function renderGradeBlock(g, opts = {}) {
|
|||
}
|
||||
|
||||
const cm = g.centering_measurement || null;
|
||||
const centeringLine = cm
|
||||
? `measured — left/right ${cm.horizontal_label} · top/bottom ${cm.vertical_label}` : '';
|
||||
const centeringLine = cm && cm.reliable === false
|
||||
? `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
|
||||
// a percent or two of standard and repeating that number on every card
|
||||
|
|
|
|||
23
vision.py
23
vision.py
|
|
@ -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 \
|
||||
card can still be pristine, and PSA may grade it strongly with an OC \
|
||||
(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 \
|
||||
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 \
|
||||
|
|
@ -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
|
||||
|
||||
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(
|
||||
"MEASURED CENTERING — computed from the pixels by finding the "
|
||||
"border on each side of the card:\n"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue