FAILURE MAP
← Case archive

FA-79291 / Image orientation metadata / Open access

Canonical orientation picks the highest tag on symmetric images · case 01

Symmetric logos are stored with a different canonical tag on each upload, breaking dedup provenance.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Ties replace the current best, so the last equal orientation wins.

VERIFIED REPAIR

Keep the first (lowest) tag when keys are equal.

Unsuccessful approach: Special-casing tag 8 still lets ties between lower tags pick the wrong one.

Case contract

For duplicate detection, bring a pixel grid into a canonical orientation: of the eight orientations (tags 1..8, same pixel mapping as EXIF baking), pick the one whose key (height, width, row-major pixels) is smallest; ties go to the lowest tag. Return [tag, inverse_tag, canonical_grid] where the inverse tag restores the original (6 and 8 are mutual inverses, every other tag is self-inverse).

Why this case matters

Camera, phone and scanner images carry an orientation hint separately from the stored pixels; galleries, thumbnailers, editors and upload pipelines must interpret it consistently or photos appear sideways, mirrored or doubly rotated.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(grid):
    h = len(grid)
    w = len(grid[0])
    def src(t, r, c):
        return {1: (r, c), 2: (r, w - 1 - c), 3: (h - 1 - r, w - 1 - c), 4: (h - 1 - r, c),
                5: (c, r), 6: (h - 1 - c, r), 7: (h - 1 - c, w - 1 - r), 8: (c, w - 1 - r)}[t]
    best = None
    for t in range(1, 9):
        oh, ow = (w, h) if t >= 5 else (h, w)
        img = [[grid[src(t, r, c)[0]][src(t, r, c)[1]] for c in range(ow)] for r in range(oh)]
        key = (oh, ow, [v for row in img for v in row])
        if best is None or key <= best[0]:
            best = (key, t, img)
    inverse = {6: 8, 8: 6}.get(best[1], best[1])
    return [best[1], inverse, best[2]]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[[4, 7, 0]], [2, 2, [[0, 7, 4]]]], [[[7]], [1, 1, [[7]]]], [[[6], [0], [9]], [5, 5, [[6, 0, 9]]]], [[[0, 0], [1, 1], [0, 1]], [5, 5, [[0, 1, 0], [0, 1, 1]]]], [[[6, 3, 0], [0, 8, 5], [1, 5, 5]], [2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]]], [[[6, 3, 1], [6, 1, 9]], [2, 2, [[1, 3, 6], [9, 1, 6]]]], [[[0, 3, 2], [3, 9, 8]], [1, 1, [[0, 3, 2], [3, 9, 8]]]], [[[0, 1, 1]], [1, 1, [[0, 1, 1]]]]], [[[[4], [3], [0]], [6, 8, [[0, 3, 4]]]], [[[9]], [1, 1, [[9]]]], [[[0], [1]], [5, 5, [[0, 1]]]], [[[0, 0, 0], [0, 1, 1]], [1, 1, [[0, 0, 0], [0, 1, 1]]]], [[[2, 2], [0, 5]], [6, 8, [[0, 2], [5, 2]]]], [[[1, 1], [4, 4], [9, 1]], [8, 6, [[1, 4, 1], [1, 4, 9]]]], [[[8, 6, 5], [0, 7, 5], [2, 0, 8]], [4, 4, [[2, 0, 8], [0, 7, 5], [8, 6, 5]]]], [[[1, 0], [1, 0]], [7, 7, [[0, 0], [1, 1]]]]], [[[[4], [7], [2]], [6, 8, [[2, 7, 4]]]], [[[0], [0]], [5, 5, [[0, 0]]]], [[[0, 0], [1, 0]], [2, 2, [[0, 0], [0, 1]]]], [[[4, 0, 8], [5, 8, 1], [9, 9, 6]], [1, 1, [[4, 0, 8], [5, 8, 1], [9, 9, 6]]]], [[[6, 8], [1, 1], [3, 2]], [7, 7, [[2, 1, 8], [3, 1, 6]]]], [[[0, 0], [1, 0], [1, 1]], [8, 6, [[0, 0, 1], [0, 1, 1]]]], [[[5, 3, 2], [4, 4, 1]], [3, 3, [[1, 4, 4], [2, 3, 5]]]], [[[5], [4]], [6, 8, [[4, 5]]]]], [[[[5, 9, 8]], [1, 1, [[5, 9, 8]]]], [[[5], [7]], [5, 5, [[5, 7]]]], [[[0, 1, 0], [1, 1, 1], [0, 1, 0]], [1, 1, [[0, 1, 0], [1, 1, 1], [0, 1, 0]]]], [[[9, 4], [2, 1]], [3, 3, [[1, 2], [4, 9]]]], [[[5, 5, 3], [0, 8, 7]], [4, 4, [[0, 8, 7], [5, 5, 3]]]], [[[5, 7, 3], [2, 3, 7]], [4, 4, [[2, 3, 7], [5, 7, 3]]]], [[[9, 6, 1], [5, 6, 6], [8, 1, 0]], [3, 3, [[0, 1, 8], [6, 6, 5], [1, 6, 9]]]], [[[7]], [1, 1, [[7]]]]], [[[[3, 0, 4]], [1, 1, [[3, 0, 4]]]], [[[1]], [1, 1, [[1]]]], [[[1], [1], [1]], [5, 5, [[1, 1, 1]]]], [[[2, 0, 5], [5, 0, 3], [2, 8, 2]], [1, 1, [[2, 0, 5], [5, 0, 3], [2, 8, 2]]]], [[[6, 8, 5], [2, 4, 8]], [4, 4, [[2, 4, 8], [6, 8, 5]]]], [[[1, 2, 8], [3, 9, 8]], [1, 1, [[1, 2, 8], [3, 9, 8]]]], [[[5, 9, 6], [7, 4, 4]], [3, 3, [[4, 4, 7], [6, 9, 5]]]], [[[5, 3], [9, 5]], [2, 2, [[3, 5], [5, 9]]]]]]
labels = ["regression: tie between symmetric orientations", "repair trap", "combined fault", "control", "control", "boundary", "boundary", "control"]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("%s %d" % (labels[i % len(labels)], i), solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: tie between symmetric orientations 0[3, 3, [[0, 7, 4]]][2, 2, [[0, 7, 4]]]Failed
repair trap 1[8, 6, [[7]]][1, 1, [[7]]]Failed
combined fault 2[8, 6, [[6, 0, 9]]][5, 5, [[6, 0, 9]]]Failed
control 3[5, 5, [[0, 1, 0], [0, 1, 1]]][5, 5, [[0, 1, 0], [0, 1, 1]]]Passed
control 4[2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]][2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]]Passed
boundary 5[2, 2, [[1, 3, 6], [9, 1, 6]]][2, 2, [[1, 3, 6], [9, 1, 6]]]Passed
boundary 6[1, 1, [[0, 3, 2], [3, 9, 8]]][1, 1, [[0, 3, 2], [3, 9, 8]]]Passed
control 7[4, 4, [[0, 1, 1]]][1, 1, [[0, 1, 1]]]Failed

SHA-256 / bed156702cc357e5fad3b69b9265fcc52773bae6e4afc4c7c36411f3e3b1830c

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(grid):
    h = len(grid)
    w = len(grid[0])
    def src(t, r, c):
        return {1: (r, c), 2: (r, w - 1 - c), 3: (h - 1 - r, w - 1 - c), 4: (h - 1 - r, c),
                5: (c, r), 6: (h - 1 - c, r), 7: (h - 1 - c, w - 1 - r), 8: (c, w - 1 - r)}[t]
    best = None
    for t in range(1, 9):
        oh, ow = (w, h) if t >= 5 else (h, w)
        img = [[grid[src(t, r, c)[0]][src(t, r, c)[1]] for c in range(ow)] for r in range(oh)]
        key = (oh, ow, [v for row in img for v in row])
        if best is None or key < best[0] or (key == best[0] and t == 8):
            best = (key, t, img)
    inverse = {6: 8, 8: 6}.get(best[1], best[1])
    return [best[1], inverse, best[2]]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[[4, 7, 0]], [2, 2, [[0, 7, 4]]]], [[[7]], [1, 1, [[7]]]], [[[6], [0], [9]], [5, 5, [[6, 0, 9]]]], [[[0, 0], [1, 1], [0, 1]], [5, 5, [[0, 1, 0], [0, 1, 1]]]], [[[6, 3, 0], [0, 8, 5], [1, 5, 5]], [2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]]], [[[6, 3, 1], [6, 1, 9]], [2, 2, [[1, 3, 6], [9, 1, 6]]]], [[[0, 3, 2], [3, 9, 8]], [1, 1, [[0, 3, 2], [3, 9, 8]]]], [[[0, 1, 1]], [1, 1, [[0, 1, 1]]]]], [[[[4], [3], [0]], [6, 8, [[0, 3, 4]]]], [[[9]], [1, 1, [[9]]]], [[[0], [1]], [5, 5, [[0, 1]]]], [[[0, 0, 0], [0, 1, 1]], [1, 1, [[0, 0, 0], [0, 1, 1]]]], [[[2, 2], [0, 5]], [6, 8, [[0, 2], [5, 2]]]], [[[1, 1], [4, 4], [9, 1]], [8, 6, [[1, 4, 1], [1, 4, 9]]]], [[[8, 6, 5], [0, 7, 5], [2, 0, 8]], [4, 4, [[2, 0, 8], [0, 7, 5], [8, 6, 5]]]], [[[1, 0], [1, 0]], [7, 7, [[0, 0], [1, 1]]]]], [[[[4], [7], [2]], [6, 8, [[2, 7, 4]]]], [[[0], [0]], [5, 5, [[0, 0]]]], [[[0, 0], [1, 0]], [2, 2, [[0, 0], [0, 1]]]], [[[4, 0, 8], [5, 8, 1], [9, 9, 6]], [1, 1, [[4, 0, 8], [5, 8, 1], [9, 9, 6]]]], [[[6, 8], [1, 1], [3, 2]], [7, 7, [[2, 1, 8], [3, 1, 6]]]], [[[0, 0], [1, 0], [1, 1]], [8, 6, [[0, 0, 1], [0, 1, 1]]]], [[[5, 3, 2], [4, 4, 1]], [3, 3, [[1, 4, 4], [2, 3, 5]]]], [[[5], [4]], [6, 8, [[4, 5]]]]], [[[[5, 9, 8]], [1, 1, [[5, 9, 8]]]], [[[5], [7]], [5, 5, [[5, 7]]]], [[[0, 1, 0], [1, 1, 1], [0, 1, 0]], [1, 1, [[0, 1, 0], [1, 1, 1], [0, 1, 0]]]], [[[9, 4], [2, 1]], [3, 3, [[1, 2], [4, 9]]]], [[[5, 5, 3], [0, 8, 7]], [4, 4, [[0, 8, 7], [5, 5, 3]]]], [[[5, 7, 3], [2, 3, 7]], [4, 4, [[2, 3, 7], [5, 7, 3]]]], [[[9, 6, 1], [5, 6, 6], [8, 1, 0]], [3, 3, [[0, 1, 8], [6, 6, 5], [1, 6, 9]]]], [[[7]], [1, 1, [[7]]]]], [[[[3, 0, 4]], [1, 1, [[3, 0, 4]]]], [[[1]], [1, 1, [[1]]]], [[[1], [1], [1]], [5, 5, [[1, 1, 1]]]], [[[2, 0, 5], [5, 0, 3], [2, 8, 2]], [1, 1, [[2, 0, 5], [5, 0, 3], [2, 8, 2]]]], [[[6, 8, 5], [2, 4, 8]], [4, 4, [[2, 4, 8], [6, 8, 5]]]], [[[1, 2, 8], [3, 9, 8]], [1, 1, [[1, 2, 8], [3, 9, 8]]]], [[[5, 9, 6], [7, 4, 4]], [3, 3, [[4, 4, 7], [6, 9, 5]]]], [[[5, 3], [9, 5]], [2, 2, [[3, 5], [5, 9]]]]]]
labels = ["regression: tie between symmetric orientations", "repair trap", "combined fault", "control", "control", "boundary", "boundary", "control"]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("%s %d" % (labels[i % len(labels)], i), solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: tie between symmetric orientations 0[2, 2, [[0, 7, 4]]][2, 2, [[0, 7, 4]]]Passed
repair trap 1[8, 6, [[7]]][1, 1, [[7]]]Failed
combined fault 2[8, 6, [[6, 0, 9]]][5, 5, [[6, 0, 9]]]Failed
control 3[5, 5, [[0, 1, 0], [0, 1, 1]]][5, 5, [[0, 1, 0], [0, 1, 1]]]Passed
control 4[2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]][2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]]Passed
boundary 5[2, 2, [[1, 3, 6], [9, 1, 6]]][2, 2, [[1, 3, 6], [9, 1, 6]]]Passed
boundary 6[1, 1, [[0, 3, 2], [3, 9, 8]]][1, 1, [[0, 3, 2], [3, 9, 8]]]Passed
control 7[1, 1, [[0, 1, 1]]][1, 1, [[0, 1, 1]]]Passed

SHA-256 / 079fd9d989c02e43c6dbbaa513446e6fb6626ce9831c33e9b17a824d86fc81a5

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(grid):
    h = len(grid)
    w = len(grid[0])
    def src(t, r, c):
        return {1: (r, c), 2: (r, w - 1 - c), 3: (h - 1 - r, w - 1 - c), 4: (h - 1 - r, c),
                5: (c, r), 6: (h - 1 - c, r), 7: (h - 1 - c, w - 1 - r), 8: (c, w - 1 - r)}[t]
    best = None
    for t in range(1, 9):
        oh, ow = (w, h) if t >= 5 else (h, w)
        img = [[grid[src(t, r, c)[0]][src(t, r, c)[1]] for c in range(ow)] for r in range(oh)]
        key = (oh, ow, [v for row in img for v in row])
        if best is None or key < best[0]:
            best = (key, t, img)
    inverse = {6: 8, 8: 6}.get(best[1], best[1])
    return [best[1], inverse, best[2]]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[[4, 7, 0]], [2, 2, [[0, 7, 4]]]], [[[7]], [1, 1, [[7]]]], [[[6], [0], [9]], [5, 5, [[6, 0, 9]]]], [[[0, 0], [1, 1], [0, 1]], [5, 5, [[0, 1, 0], [0, 1, 1]]]], [[[6, 3, 0], [0, 8, 5], [1, 5, 5]], [2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]]], [[[6, 3, 1], [6, 1, 9]], [2, 2, [[1, 3, 6], [9, 1, 6]]]], [[[0, 3, 2], [3, 9, 8]], [1, 1, [[0, 3, 2], [3, 9, 8]]]], [[[0, 1, 1]], [1, 1, [[0, 1, 1]]]]], [[[[4], [3], [0]], [6, 8, [[0, 3, 4]]]], [[[9]], [1, 1, [[9]]]], [[[0], [1]], [5, 5, [[0, 1]]]], [[[0, 0, 0], [0, 1, 1]], [1, 1, [[0, 0, 0], [0, 1, 1]]]], [[[2, 2], [0, 5]], [6, 8, [[0, 2], [5, 2]]]], [[[1, 1], [4, 4], [9, 1]], [8, 6, [[1, 4, 1], [1, 4, 9]]]], [[[8, 6, 5], [0, 7, 5], [2, 0, 8]], [4, 4, [[2, 0, 8], [0, 7, 5], [8, 6, 5]]]], [[[1, 0], [1, 0]], [7, 7, [[0, 0], [1, 1]]]]], [[[[4], [7], [2]], [6, 8, [[2, 7, 4]]]], [[[0], [0]], [5, 5, [[0, 0]]]], [[[0, 0], [1, 0]], [2, 2, [[0, 0], [0, 1]]]], [[[4, 0, 8], [5, 8, 1], [9, 9, 6]], [1, 1, [[4, 0, 8], [5, 8, 1], [9, 9, 6]]]], [[[6, 8], [1, 1], [3, 2]], [7, 7, [[2, 1, 8], [3, 1, 6]]]], [[[0, 0], [1, 0], [1, 1]], [8, 6, [[0, 0, 1], [0, 1, 1]]]], [[[5, 3, 2], [4, 4, 1]], [3, 3, [[1, 4, 4], [2, 3, 5]]]], [[[5], [4]], [6, 8, [[4, 5]]]]], [[[[5, 9, 8]], [1, 1, [[5, 9, 8]]]], [[[5], [7]], [5, 5, [[5, 7]]]], [[[0, 1, 0], [1, 1, 1], [0, 1, 0]], [1, 1, [[0, 1, 0], [1, 1, 1], [0, 1, 0]]]], [[[9, 4], [2, 1]], [3, 3, [[1, 2], [4, 9]]]], [[[5, 5, 3], [0, 8, 7]], [4, 4, [[0, 8, 7], [5, 5, 3]]]], [[[5, 7, 3], [2, 3, 7]], [4, 4, [[2, 3, 7], [5, 7, 3]]]], [[[9, 6, 1], [5, 6, 6], [8, 1, 0]], [3, 3, [[0, 1, 8], [6, 6, 5], [1, 6, 9]]]], [[[7]], [1, 1, [[7]]]]], [[[[3, 0, 4]], [1, 1, [[3, 0, 4]]]], [[[1]], [1, 1, [[1]]]], [[[1], [1], [1]], [5, 5, [[1, 1, 1]]]], [[[2, 0, 5], [5, 0, 3], [2, 8, 2]], [1, 1, [[2, 0, 5], [5, 0, 3], [2, 8, 2]]]], [[[6, 8, 5], [2, 4, 8]], [4, 4, [[2, 4, 8], [6, 8, 5]]]], [[[1, 2, 8], [3, 9, 8]], [1, 1, [[1, 2, 8], [3, 9, 8]]]], [[[5, 9, 6], [7, 4, 4]], [3, 3, [[4, 4, 7], [6, 9, 5]]]], [[[5, 3], [9, 5]], [2, 2, [[3, 5], [5, 9]]]]]]
labels = ["regression: tie between symmetric orientations", "repair trap", "combined fault", "control", "control", "boundary", "boundary", "control"]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check("%s %d" % (labels[i % len(labels)], i), solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: tie between symmetric orientations 0[2, 2, [[0, 7, 4]]][2, 2, [[0, 7, 4]]]Passed
repair trap 1[1, 1, [[7]]][1, 1, [[7]]]Passed
combined fault 2[5, 5, [[6, 0, 9]]][5, 5, [[6, 0, 9]]]Passed
control 3[5, 5, [[0, 1, 0], [0, 1, 1]]][5, 5, [[0, 1, 0], [0, 1, 1]]]Passed
control 4[2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]][2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]]Passed
boundary 5[2, 2, [[1, 3, 6], [9, 1, 6]]][2, 2, [[1, 3, 6], [9, 1, 6]]]Passed
boundary 6[1, 1, [[0, 3, 2], [3, 9, 8]]][1, 1, [[0, 3, 2], [3, 9, 8]]]Passed
control 7[1, 1, [[0, 1, 1]]][1, 1, [[0, 1, 1]]]Passed

SHA-256 / b17276a69fd96ec45f2677b81110075862264a8fba65135c885c8e15622d2ba8

Verification & scope

A deterministic bounded teaching model with a stipulated contract; it makes no claim of conformance to any published specification. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:49:43.154066+00:00.

Case digest / 8e71943d2b510b449a5c6acfef70fddd2f0a07aa31ce3efb44c695f48578af67