Add aspect-ratio measurement as a soft trimming signal, reported against every standard card size
This commit is contained in:
parent
b28305dd25
commit
f464faca43
5 changed files with 139 additions and 2 deletions
75
cardimage.py
75
cardimage.py
|
|
@ -66,6 +66,17 @@ MIN_SOURCE_PX = 600
|
|||
CORNERS = ("top-left", "top-right", "bottom-left", "bottom-right")
|
||||
EDGES = ("top-edge", "right-edge", "bottom-edge", "left-edge")
|
||||
|
||||
# Short-side/long-side ratios for card stock sizes actually in circulation.
|
||||
# Trimming is compared against whichever of these is closest, not one fixed
|
||||
# number — treating every card as one standard size would flag genuinely
|
||||
# factory-cut cards (a tobacco-era T206, a wide 1930s strip card) as trimmed
|
||||
# just for being a different shape than a modern card.
|
||||
STANDARD_ASPECT_RATIOS = {
|
||||
"modern (2.5\" x 3.5\", most post-1957 issues)": 2.5 / 3.5,
|
||||
"tobacco-era (roughly 1.5\" x 2.5\", T206 and similar pre-1920s)": 1.5 / 2.5,
|
||||
"wide vintage (roughly 2.0\" x 3.0\", some 1930s-50s strip/premium issues)": 2.0 / 3.0,
|
||||
}
|
||||
|
||||
|
||||
def available():
|
||||
return Image is not None
|
||||
|
|
@ -612,6 +623,70 @@ def centering_profile(image_bytes):
|
|||
return None
|
||||
|
||||
|
||||
def aspect_profile(image_bytes):
|
||||
"""Measure the card's own width:height ratio, as a soft signal for trimming.
|
||||
|
||||
Unlike centering and edge whitening, this deliberately does NOT gate
|
||||
itself off with a reliability check the way those do — there isn't one
|
||||
available. Those two can tell a structurally bad photo apart from a bad
|
||||
card (a foil border, an angled shot) from pixel evidence alone. This
|
||||
measurement can't: an axis-aligned bounding box can't distinguish "this
|
||||
card is genuinely a non-standard shape" from "this card was photographed
|
||||
slightly rotated in frame", since both inflate the box the same way. That
|
||||
judgement needs the photo itself, which only the vision model has — so
|
||||
the number is always returned, and the prompt is the place trimming vs.
|
||||
photo-angle gets decided, the same way it already decides a print line
|
||||
from a crease.
|
||||
|
||||
Returns the deviation against EVERY standard size, not just the nearest
|
||||
one. Collapsing to "closest standard" was tried first and measurably
|
||||
backfired: the three standards sit only 5-11% apart, close enough that a
|
||||
real few-percent trim on a modern card lands nearer the vintage standard
|
||||
than its own, and reports as clean. Which standard is actually relevant
|
||||
depends on the card's era — something only the vision model determines,
|
||||
from the same photo, after this function has already run — so the
|
||||
honest fix is hand over all three deviations and let it pick the one
|
||||
that matches the card it can see, the same division of labour as every
|
||||
other measurement here.
|
||||
|
||||
Only catches UNEVEN trimming — shaving more off one side than another
|
||||
distorts the ratio. A trim taken symmetrically off all four sides
|
||||
preserves the ratio while shrinking the whole card, and nothing here can
|
||||
catch that without a size reference (a ruler, a coin) in the photo.
|
||||
"""
|
||||
if Image is None:
|
||||
return None
|
||||
try:
|
||||
img = Image.open(io.BytesIO(image_bytes))
|
||||
img.load()
|
||||
box = _detect_card_box(img)
|
||||
if not box:
|
||||
return None
|
||||
bw, bh = box[2] - box[0], box[3] - box[1]
|
||||
if bw < 40 or bh < 40:
|
||||
return None
|
||||
ratio = min(bw, bh) / float(max(bw, bh))
|
||||
|
||||
against_standards = {
|
||||
name: {
|
||||
"standard_ratio": round(std_ratio, 4),
|
||||
"deviation_percent": round(abs(ratio - std_ratio) / std_ratio * 100.0, 1),
|
||||
}
|
||||
for name, std_ratio in STANDARD_ASPECT_RATIOS.items()
|
||||
}
|
||||
best_name = min(against_standards, key=lambda n: against_standards[n]["deviation_percent"])
|
||||
|
||||
return {
|
||||
"measured_ratio": round(ratio, 4),
|
||||
"width_px": bw,
|
||||
"height_px": bh,
|
||||
"against_standards": against_standards,
|
||||
"best_match": best_name,
|
||||
}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _edge_enhanced(strip):
|
||||
"""Whitening map of an edge strip, keyed on colour saturation.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue