FA-79311 / Image orientation metadata / Open access
Canonical restore tag treats quarter turns as self-inverse · case 01
Restoring a deduplicated photo turns it a half turn away from the upload.
ROOT CAUSE
The inverse of a quarter turn is reported as the same quarter turn.
VERIFIED REPAIR
Map 6 to 8 and 8 to 6; all other orientations are involutions.
Unsuccessful approach: Transpose and transverse are their own inverses; exchanging them is wrong.
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 = 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 = [[[[[5, 2], [9, 7], [6, 9]], [8, 6, [[2, 7, 9], [5, 9, 6]]]], [[[0, 0], [1, 1], [0, 1]], [5, 5, [[0, 1, 0], [0, 1, 1]]]], [[[3, 1], [2, 6], [9, 2]], [8, 6, [[1, 6, 2], [3, 2, 9]]]], [[[4, 7, 0]], [2, 2, [[0, 7, 4]]]], [[[6, 3, 0], [0, 8, 5], [1, 5, 5]], [2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]]], [[[7]], [1, 1, [[7]]]], [[[0, 1, 1]], [1, 1, [[0, 1, 1]]]], [[[0, 1], [0, 0], [0, 0]], [6, 8, [[0, 0, 0], [0, 0, 1]]]]], [[[[4], [3], [0]], [6, 8, [[0, 3, 4]]]], [[[9], [8]], [6, 8, [[8, 9]]]], [[[4, 4]], [1, 1, [[4, 4]]]], [[[0, 0, 0], [0, 1, 1]], [1, 1, [[0, 0, 0], [0, 1, 1]]]], [[[9]], [1, 1, [[9]]]], [[[1, 0, 1]], [1, 1, [[1, 0, 1]]]], [[[8, 6, 5], [0, 7, 5], [2, 0, 8]], [4, 4, [[2, 0, 8], [0, 7, 5], [8, 6, 5]]]], [[[1, 1], [4, 4], [9, 1]], [8, 6, [[1, 4, 1], [1, 4, 9]]]]], [[[[4], [7], [2]], [6, 8, [[2, 7, 4]]]], [[[5], [4]], [6, 8, [[4, 5]]]], [[[8, 9], [6, 5], [4, 8]], [6, 8, [[4, 6, 8], [8, 5, 9]]]], [[[0, 3]], [1, 1, [[0, 3]]]], [[[1, 0, 0], [1, 0, 0]], [2, 2, [[0, 0, 1], [0, 0, 1]]]], [[[6, 5, 2]], [2, 2, [[2, 5, 6]]]], [[[4, 0, 8], [5, 8, 1], [9, 9, 6]], [1, 1, [[4, 0, 8], [5, 8, 1], [9, 9, 6]]]], [[[0, 0], [1, 0], [1, 1]], [8, 6, [[0, 0, 1], [0, 1, 1]]]]], [[[[1, 1], [8, 1], [9, 9]], [8, 6, [[1, 1, 9], [1, 8, 9]]]], [[[4], [5], [0]], [6, 8, [[0, 5, 4]]]], [[[8], [4], [3]], [6, 8, [[3, 4, 8]]]], [[[8, 7, 3], [3, 8, 4]], [2, 2, [[3, 7, 8], [4, 8, 3]]]], [[[3, 8, 5]], [1, 1, [[3, 8, 5]]]], [[[3, 8]], [1, 1, [[3, 8]]]], [[[9, 4], [2, 1]], [3, 3, [[1, 2], [4, 9]]]], [[[6, 7, 5], [1, 9, 6], [6, 4, 6]], [8, 6, [[5, 6, 6], [7, 9, 4], [6, 1, 6]]]]], [[[[5, 0], [5, 5], [8, 7]], [8, 6, [[0, 5, 7], [5, 5, 8]]]], [[[0], [1], [0]], [5, 5, [[0, 1, 0]]]], [[[3], [1]], [6, 8, [[1, 3]]]], [[[3, 2, 0], [2, 8, 5], [9, 0, 9]], [2, 2, [[0, 2, 3], [5, 8, 2], [9, 0, 9]]]], [[[4, 9, 9], [3, 6, 8]], [4, 4, [[3, 6, 8], [4, 9, 9]]]], [[[3, 0, 4]], [1, 1, [[3, 0, 4]]]], [[[2, 0, 5], [5, 0, 3], [2, 8, 2]], [1, 1, [[2, 0, 5], [5, 0, 3], [2, 8, 2]]]], [[[9, 6], [3, 3], [3, 5]], [6, 8, [[3, 3, 9], [5, 3, 6]]]]]]
labels = ["regression: restoring inverse tag", "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: restoring inverse tag 0 | [8, 8, [[2, 7, 9], [5, 9, 6]]] | [8, 6, [[2, 7, 9], [5, 9, 6]]] | Failed |
| repair trap 1 | [5, 5, [[0, 1, 0], [0, 1, 1]]] | [5, 5, [[0, 1, 0], [0, 1, 1]]] | Passed |
| combined fault 2 | [8, 8, [[1, 6, 2], [3, 2, 9]]] | [8, 6, [[1, 6, 2], [3, 2, 9]]] | Failed |
| control 3 | [2, 2, [[0, 7, 4]]] | [2, 2, [[0, 7, 4]]] | 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 | [1, 1, [[7]]] | [1, 1, [[7]]] | Passed |
| boundary 6 | [1, 1, [[0, 1, 1]]] | [1, 1, [[0, 1, 1]]] | Passed |
| control 7 | [6, 6, [[0, 0, 0], [0, 0, 1]]] | [6, 8, [[0, 0, 0], [0, 0, 1]]] | Failed |
SHA-256 / c2817685287bd65e4c9d1dcce2d8ffc4e293f60c2aa1a285cadfb95cf5d6b543
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]:
best = (key, t, img)
inverse = {5: 7, 7: 5}.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 = [[[[[5, 2], [9, 7], [6, 9]], [8, 6, [[2, 7, 9], [5, 9, 6]]]], [[[0, 0], [1, 1], [0, 1]], [5, 5, [[0, 1, 0], [0, 1, 1]]]], [[[3, 1], [2, 6], [9, 2]], [8, 6, [[1, 6, 2], [3, 2, 9]]]], [[[4, 7, 0]], [2, 2, [[0, 7, 4]]]], [[[6, 3, 0], [0, 8, 5], [1, 5, 5]], [2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]]], [[[7]], [1, 1, [[7]]]], [[[0, 1, 1]], [1, 1, [[0, 1, 1]]]], [[[0, 1], [0, 0], [0, 0]], [6, 8, [[0, 0, 0], [0, 0, 1]]]]], [[[[4], [3], [0]], [6, 8, [[0, 3, 4]]]], [[[9], [8]], [6, 8, [[8, 9]]]], [[[4, 4]], [1, 1, [[4, 4]]]], [[[0, 0, 0], [0, 1, 1]], [1, 1, [[0, 0, 0], [0, 1, 1]]]], [[[9]], [1, 1, [[9]]]], [[[1, 0, 1]], [1, 1, [[1, 0, 1]]]], [[[8, 6, 5], [0, 7, 5], [2, 0, 8]], [4, 4, [[2, 0, 8], [0, 7, 5], [8, 6, 5]]]], [[[1, 1], [4, 4], [9, 1]], [8, 6, [[1, 4, 1], [1, 4, 9]]]]], [[[[4], [7], [2]], [6, 8, [[2, 7, 4]]]], [[[5], [4]], [6, 8, [[4, 5]]]], [[[8, 9], [6, 5], [4, 8]], [6, 8, [[4, 6, 8], [8, 5, 9]]]], [[[0, 3]], [1, 1, [[0, 3]]]], [[[1, 0, 0], [1, 0, 0]], [2, 2, [[0, 0, 1], [0, 0, 1]]]], [[[6, 5, 2]], [2, 2, [[2, 5, 6]]]], [[[4, 0, 8], [5, 8, 1], [9, 9, 6]], [1, 1, [[4, 0, 8], [5, 8, 1], [9, 9, 6]]]], [[[0, 0], [1, 0], [1, 1]], [8, 6, [[0, 0, 1], [0, 1, 1]]]]], [[[[1, 1], [8, 1], [9, 9]], [8, 6, [[1, 1, 9], [1, 8, 9]]]], [[[4], [5], [0]], [6, 8, [[0, 5, 4]]]], [[[8], [4], [3]], [6, 8, [[3, 4, 8]]]], [[[8, 7, 3], [3, 8, 4]], [2, 2, [[3, 7, 8], [4, 8, 3]]]], [[[3, 8, 5]], [1, 1, [[3, 8, 5]]]], [[[3, 8]], [1, 1, [[3, 8]]]], [[[9, 4], [2, 1]], [3, 3, [[1, 2], [4, 9]]]], [[[6, 7, 5], [1, 9, 6], [6, 4, 6]], [8, 6, [[5, 6, 6], [7, 9, 4], [6, 1, 6]]]]], [[[[5, 0], [5, 5], [8, 7]], [8, 6, [[0, 5, 7], [5, 5, 8]]]], [[[0], [1], [0]], [5, 5, [[0, 1, 0]]]], [[[3], [1]], [6, 8, [[1, 3]]]], [[[3, 2, 0], [2, 8, 5], [9, 0, 9]], [2, 2, [[0, 2, 3], [5, 8, 2], [9, 0, 9]]]], [[[4, 9, 9], [3, 6, 8]], [4, 4, [[3, 6, 8], [4, 9, 9]]]], [[[3, 0, 4]], [1, 1, [[3, 0, 4]]]], [[[2, 0, 5], [5, 0, 3], [2, 8, 2]], [1, 1, [[2, 0, 5], [5, 0, 3], [2, 8, 2]]]], [[[9, 6], [3, 3], [3, 5]], [6, 8, [[3, 3, 9], [5, 3, 6]]]]]]
labels = ["regression: restoring inverse tag", "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: restoring inverse tag 0 | [8, 8, [[2, 7, 9], [5, 9, 6]]] | [8, 6, [[2, 7, 9], [5, 9, 6]]] | Failed |
| repair trap 1 | [5, 7, [[0, 1, 0], [0, 1, 1]]] | [5, 5, [[0, 1, 0], [0, 1, 1]]] | Failed |
| combined fault 2 | [8, 8, [[1, 6, 2], [3, 2, 9]]] | [8, 6, [[1, 6, 2], [3, 2, 9]]] | Failed |
| control 3 | [2, 2, [[0, 7, 4]]] | [2, 2, [[0, 7, 4]]] | 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 | [1, 1, [[7]]] | [1, 1, [[7]]] | Passed |
| boundary 6 | [1, 1, [[0, 1, 1]]] | [1, 1, [[0, 1, 1]]] | Passed |
| control 7 | [6, 6, [[0, 0, 0], [0, 0, 1]]] | [6, 8, [[0, 0, 0], [0, 0, 1]]] | Failed |
SHA-256 / 914a76d0cd32741235fff18db5c01e790393537f78e197b04325532019f93a5f
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 = [[[[[5, 2], [9, 7], [6, 9]], [8, 6, [[2, 7, 9], [5, 9, 6]]]], [[[0, 0], [1, 1], [0, 1]], [5, 5, [[0, 1, 0], [0, 1, 1]]]], [[[3, 1], [2, 6], [9, 2]], [8, 6, [[1, 6, 2], [3, 2, 9]]]], [[[4, 7, 0]], [2, 2, [[0, 7, 4]]]], [[[6, 3, 0], [0, 8, 5], [1, 5, 5]], [2, 2, [[0, 3, 6], [5, 8, 0], [5, 5, 1]]]], [[[7]], [1, 1, [[7]]]], [[[0, 1, 1]], [1, 1, [[0, 1, 1]]]], [[[0, 1], [0, 0], [0, 0]], [6, 8, [[0, 0, 0], [0, 0, 1]]]]], [[[[4], [3], [0]], [6, 8, [[0, 3, 4]]]], [[[9], [8]], [6, 8, [[8, 9]]]], [[[4, 4]], [1, 1, [[4, 4]]]], [[[0, 0, 0], [0, 1, 1]], [1, 1, [[0, 0, 0], [0, 1, 1]]]], [[[9]], [1, 1, [[9]]]], [[[1, 0, 1]], [1, 1, [[1, 0, 1]]]], [[[8, 6, 5], [0, 7, 5], [2, 0, 8]], [4, 4, [[2, 0, 8], [0, 7, 5], [8, 6, 5]]]], [[[1, 1], [4, 4], [9, 1]], [8, 6, [[1, 4, 1], [1, 4, 9]]]]], [[[[4], [7], [2]], [6, 8, [[2, 7, 4]]]], [[[5], [4]], [6, 8, [[4, 5]]]], [[[8, 9], [6, 5], [4, 8]], [6, 8, [[4, 6, 8], [8, 5, 9]]]], [[[0, 3]], [1, 1, [[0, 3]]]], [[[1, 0, 0], [1, 0, 0]], [2, 2, [[0, 0, 1], [0, 0, 1]]]], [[[6, 5, 2]], [2, 2, [[2, 5, 6]]]], [[[4, 0, 8], [5, 8, 1], [9, 9, 6]], [1, 1, [[4, 0, 8], [5, 8, 1], [9, 9, 6]]]], [[[0, 0], [1, 0], [1, 1]], [8, 6, [[0, 0, 1], [0, 1, 1]]]]], [[[[1, 1], [8, 1], [9, 9]], [8, 6, [[1, 1, 9], [1, 8, 9]]]], [[[4], [5], [0]], [6, 8, [[0, 5, 4]]]], [[[8], [4], [3]], [6, 8, [[3, 4, 8]]]], [[[8, 7, 3], [3, 8, 4]], [2, 2, [[3, 7, 8], [4, 8, 3]]]], [[[3, 8, 5]], [1, 1, [[3, 8, 5]]]], [[[3, 8]], [1, 1, [[3, 8]]]], [[[9, 4], [2, 1]], [3, 3, [[1, 2], [4, 9]]]], [[[6, 7, 5], [1, 9, 6], [6, 4, 6]], [8, 6, [[5, 6, 6], [7, 9, 4], [6, 1, 6]]]]], [[[[5, 0], [5, 5], [8, 7]], [8, 6, [[0, 5, 7], [5, 5, 8]]]], [[[0], [1], [0]], [5, 5, [[0, 1, 0]]]], [[[3], [1]], [6, 8, [[1, 3]]]], [[[3, 2, 0], [2, 8, 5], [9, 0, 9]], [2, 2, [[0, 2, 3], [5, 8, 2], [9, 0, 9]]]], [[[4, 9, 9], [3, 6, 8]], [4, 4, [[3, 6, 8], [4, 9, 9]]]], [[[3, 0, 4]], [1, 1, [[3, 0, 4]]]], [[[2, 0, 5], [5, 0, 3], [2, 8, 2]], [1, 1, [[2, 0, 5], [5, 0, 3], [2, 8, 2]]]], [[[9, 6], [3, 3], [3, 5]], [6, 8, [[3, 3, 9], [5, 3, 6]]]]]]
labels = ["regression: restoring inverse tag", "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: restoring inverse tag 0 | [8, 6, [[2, 7, 9], [5, 9, 6]]] | [8, 6, [[2, 7, 9], [5, 9, 6]]] | Passed |
| repair trap 1 | [5, 5, [[0, 1, 0], [0, 1, 1]]] | [5, 5, [[0, 1, 0], [0, 1, 1]]] | Passed |
| combined fault 2 | [8, 6, [[1, 6, 2], [3, 2, 9]]] | [8, 6, [[1, 6, 2], [3, 2, 9]]] | Passed |
| control 3 | [2, 2, [[0, 7, 4]]] | [2, 2, [[0, 7, 4]]] | 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 | [1, 1, [[7]]] | [1, 1, [[7]]] | Passed |
| boundary 6 | [1, 1, [[0, 1, 1]]] | [1, 1, [[0, 1, 1]]] | Passed |
| control 7 | [6, 8, [[0, 0, 0], [0, 0, 1]]] | [6, 8, [[0, 0, 0], [0, 0, 1]]] | Passed |
SHA-256 / 9cfdf5468f6dc3bb734a854c86190a98653510fdddb7d0d81b8d1cc5c70bad30
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.351227+00:00.
Case digest / e1a328969e09fd361191faa2637343ef6d247bb615de0e904c9f443ac5d1ab2f