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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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