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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue