fix(import): Foto-Generations-Shift beim Stammbaum-Import beheben
Fotos liegen in den Stammbaum-xlsx je nach Datei links, rechts oder auf der Namenszelle. Die alte Seiten-Erkennung hing an festen Spalten (col 1 / col 4): Blätter ohne solche Anker wurden als Rechts-Layout fehlgedeutet, wodurch jedes Foto eine Generation zum Probanden hin verrutschte. Folge: Kazuya trug das Foto seines Vaters (Wilbur), Wilbur das seines Großvaters (Elay), Elay hatte keins. Fix (extract._attach_photos): beide Interpretationen (links/rechts) durchrechnen und die wählen, die jedes Foto am nächsten an die Namensspalte seines Tiers legt (minimale horizontale Fehlausrichtung). Diagnose über alle 42 Dateien: keine ist echt rechts-seitig; ~21 waren fehlklassifiziert. Selbstjustierend statt fester Spalten-Annahme. Regressionstest in test_extract.py: Kazuya ohne Foto, Wilbur/Elay mit ihren eigenen (gated auf vorhandene Quelldatei). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -341,14 +341,22 @@ def _attach_photos(z, sheets, animals, fname):
|
||||
if not anchors:
|
||||
return
|
||||
|
||||
# Detect left_style: whether photo is to the left or to the right of the name cell
|
||||
a_count = sum(1 for a in anchors if a[1] == 1)
|
||||
d_count = sum(1 for a in anchors if a[1] == 4)
|
||||
has_proband_in_d = any(a[1] == 4 and 50 <= a[2] <= 70 for a in anchors)
|
||||
left_style = a_count > 0 or (d_count > 0 and not has_proband_in_d)
|
||||
by_gen = {}
|
||||
for a in animals:
|
||||
by_gen.setdefault(a["_gen"], []).append(a)
|
||||
media_dir = os.path.join(OUT, "photos")
|
||||
col_offset = 0 if any(a["_col"] == 2 for a in animals) else 3
|
||||
|
||||
def get_anchor_gen(colnum, offset=0):
|
||||
effective_col = colnum - offset
|
||||
# A photo sits one column either side of (or on) its animal's name cell;
|
||||
# the generation is read from the photo's column. Whether photos are LEFT or
|
||||
# RIGHT of the name varies, and the old fixed-column heuristic (col 1 / col 4)
|
||||
# misclassified sheets that have neither, shifting every photo one generation
|
||||
# toward the proband (e.g. „Stammbaum von Kazuya“ / „Picus Son“: Kazuya wore
|
||||
# his father's photo, the father his grandfather's). Instead, try BOTH
|
||||
# interpretations and keep the one that places photos closest to their
|
||||
# assigned animal's name column (minimal horizontal misalignment).
|
||||
def get_anchor_gen(colnum, left_style):
|
||||
effective_col = colnum - col_offset
|
||||
if left_style:
|
||||
if effective_col <= 3: return 0
|
||||
if effective_col <= 6: return 1
|
||||
@@ -364,18 +372,26 @@ def _attach_photos(z, sheets, animals, fname):
|
||||
if effective_col <= 16: return 4
|
||||
return 5
|
||||
|
||||
by_gen = {}
|
||||
for a in animals:
|
||||
by_gen.setdefault(a["_gen"], []).append(a)
|
||||
media_dir = os.path.join(OUT, "photos")
|
||||
col_offset = 0 if any(a["_col"] == 2 for a in animals) else 3
|
||||
for i, (sp, col, row, media) in enumerate(anchors):
|
||||
g = get_anchor_gen(col, col_offset)
|
||||
cands = by_gen.get(g, [])
|
||||
if not cands:
|
||||
# fall back to nearest animal by row across all gens
|
||||
cands = animals
|
||||
target = min(cands, key=lambda a: abs((a["_row"] - row) - 10)) if cands else None
|
||||
def targets_for(left_style):
|
||||
out = []
|
||||
for (sp, col, row, media) in anchors:
|
||||
cands = by_gen.get(get_anchor_gen(col, left_style), []) or animals
|
||||
out.append(min(cands, key=lambda a: abs((a["_row"] - row) - 10)) if cands else None)
|
||||
return out
|
||||
|
||||
def mean_col_offset(targets):
|
||||
vals = [abs(col - t["_col"]) for (sp, col, row, m), t in zip(anchors, targets) if t]
|
||||
return sum(vals) / len(vals) if vals else 0.0
|
||||
|
||||
left_targets = targets_for(True)
|
||||
right_targets = targets_for(False)
|
||||
targets = (
|
||||
left_targets
|
||||
if mean_col_offset(left_targets) <= mean_col_offset(right_targets)
|
||||
else right_targets
|
||||
)
|
||||
|
||||
for (sp, col, row, media), target in zip(anchors, targets):
|
||||
if not target:
|
||||
continue
|
||||
ext = os.path.splitext(media)[1] or ".img"
|
||||
|
||||
Reference in New Issue
Block a user