FA-78961 / Image orientation metadata / Open access
Editor refuses to rotate photos with a corrupt tag · case 01
Photos whose orientation value is 0 cannot be rotated at all.
ROOT CAUSE
An invalid stored tag aborts the edit instead of being treated as the normal orientation.
VERIFIED REPAIR
Normalise unknown tags to 1 and then apply the operations.
Unsuccessful approach: Clamping large values to 8 invents a rotation that was never recorded.
Case contract
A photo editor rotates and flips losslessly by rewriting only the orientation tag. An orientation is modelled as (k, m): mirror horizontally m times, then rotate k quarter turns clockwise (tag table 1:(0,0) 2:(0,1) 3:(2,0) 4:(2,1) 5:(3,1) 6:(1,0) 7:(1,1) 8:(3,0)). Editor operations act on the displayed image: cw, ccw, r180, flip_h (mirror left-right) and flip_v (mirror top-bottom). Invalid starting tags count as 1; an unknown operation returns {"error": "unknown-op", "op": op}. Return the final tag.
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(tag, ops):
to_km = {1: (0, 0), 2: (0, 1), 3: (2, 0), 4: (2, 1), 5: (3, 1), 6: (1, 0), 7: (1, 1), 8: (3, 0)}
from_km = {v: t for t, v in to_km.items()}
if tag not in to_km:
return {'error': 'bad-tag'}
k, m = to_km[tag]
for op in ops:
if op == 'cw':
k = (k + 1) % 4
elif op == 'ccw':
k = (k + 3) % 4
elif op == 'r180':
k = (k + 2) % 4
elif op == 'flip_h':
k, m = (-k) % 4, 1 - m
elif op == 'flip_v':
k, m = (2 - k) % 4, 1 - m
else:
return {'error': 'unknown-op', 'op': op}
return from_km[(k, m)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[(0, ['flip_v']), 4], [(9, ['flip_h']), 2], [(9, []), 1], [(8, ['ccw', 'flip_h', 'ccw']), 7], [(7, ['flip_v', 'ccw', 'r180']), 3], [(8, ['ccw', 'cw']), 8], [(3, ['rotate']), {'error': 'unknown-op', 'op': 'rotate'}], [(0, ['cw', 'flip_v', 'rotate']), {'error': 'unknown-op', 'op': 'rotate'}]], [[(0, []), 1], [(9, ['cw', 'flip_v', 'flip_v']), 6], [(11, ['flip_v', 'ccw', 'flip_h', 'flip_v']), 5], [(8, ['ccw', 'r180']), 1], [(4, ['mirror', 'flip_v', 'cw', 'r180']), {'error': 'unknown-op', 'op': 'mirror'}], [(2, ['flip_v', 'ccw', 'cw']), 3], [(5, ['ccw', 'mirror']), {'error': 'unknown-op', 'op': 'mirror'}], [(11, ['r180']), 3]], [[(9, ['cw', 'r180', 'flip_h']), 7], [(11, ['cw', 'flip_v', 'flip_h']), 8], [(9, ['flip_v', 'flip_v', 'flip_v', 'cw']), 5], [(1, ['ccw', 'ccw', 'r180']), 1], [(5, ['flip_h']), 6], [(2, ['flip_v', 'cw', 'cw']), 1], [(6, ['r180', 'flip_v', 'ccw']), 4], [(11, ['r180', 'cw', 'flip_v', 'cw']), 2]], [[(9, ['ccw', 'flip_h', 'flip_v']), 6], [(9, ['flip_h', 'flip_h', 'flip_h', 'cw']), 7], [(9, ['cw']), 6], [(4, ['ccw']), 7], [(8, ['rotate']), {'error': 'unknown-op', 'op': 'rotate'}], [(4, ['ccw', 'ccw', 'flip_v', 'ccw']), 6], [(1, ['r180', 'flip_h', 'cw', 'flip_v']), 8], [(0, ['r180', 'ccw', 'cw', 'cw']), 8]], [[(0, ['flip_v', 'cw']), 5], [(9, ['ccw', 'cw', 'flip_v']), 4], [(11, ['r180']), 3], [(1, []), 1], [(5, ['r180']), 7], [(6, ['ccw']), 1], [(7, ['CW', 'r180', 'r180', 'flip_h']), {'error': 'unknown-op', 'op': 'CW'}], [(11, ['cw', 'cw', 'ccw']), 6]]]
labels = ["regression: invalid starting 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: invalid starting tag 0 | {'error': 'bad-tag'} | 4 | Failed |
| repair trap 1 | {'error': 'bad-tag'} | 2 | Failed |
| combined fault 2 | {'error': 'bad-tag'} | 1 | Failed |
| control 3 | 7 | 7 | Passed |
| control 4 | 3 | 3 | Passed |
| boundary 5 | 8 | 8 | Passed |
| boundary 6 | {'error': 'unknown-op', 'op': 'rotate'} | {'error': 'unknown-op', 'op': 'rotate'} | Passed |
| control 7 | {'error': 'bad-tag'} | {'error': 'unknown-op', 'op': 'rotate'} | Failed |
SHA-256 / f64047965287adbce013e3464e59159c072795fa9f3536b5663208641b1308c0
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(tag, ops):
to_km = {1: (0, 0), 2: (0, 1), 3: (2, 0), 4: (2, 1), 5: (3, 1), 6: (1, 0), 7: (1, 1), 8: (3, 0)}
from_km = {v: t for t, v in to_km.items()}
if tag not in to_km:
tag = 8 if tag > 8 else 1
k, m = to_km[tag]
for op in ops:
if op == 'cw':
k = (k + 1) % 4
elif op == 'ccw':
k = (k + 3) % 4
elif op == 'r180':
k = (k + 2) % 4
elif op == 'flip_h':
k, m = (-k) % 4, 1 - m
elif op == 'flip_v':
k, m = (2 - k) % 4, 1 - m
else:
return {'error': 'unknown-op', 'op': op}
return from_km[(k, m)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[(0, ['flip_v']), 4], [(9, ['flip_h']), 2], [(9, []), 1], [(8, ['ccw', 'flip_h', 'ccw']), 7], [(7, ['flip_v', 'ccw', 'r180']), 3], [(8, ['ccw', 'cw']), 8], [(3, ['rotate']), {'error': 'unknown-op', 'op': 'rotate'}], [(0, ['cw', 'flip_v', 'rotate']), {'error': 'unknown-op', 'op': 'rotate'}]], [[(0, []), 1], [(9, ['cw', 'flip_v', 'flip_v']), 6], [(11, ['flip_v', 'ccw', 'flip_h', 'flip_v']), 5], [(8, ['ccw', 'r180']), 1], [(4, ['mirror', 'flip_v', 'cw', 'r180']), {'error': 'unknown-op', 'op': 'mirror'}], [(2, ['flip_v', 'ccw', 'cw']), 3], [(5, ['ccw', 'mirror']), {'error': 'unknown-op', 'op': 'mirror'}], [(11, ['r180']), 3]], [[(9, ['cw', 'r180', 'flip_h']), 7], [(11, ['cw', 'flip_v', 'flip_h']), 8], [(9, ['flip_v', 'flip_v', 'flip_v', 'cw']), 5], [(1, ['ccw', 'ccw', 'r180']), 1], [(5, ['flip_h']), 6], [(2, ['flip_v', 'cw', 'cw']), 1], [(6, ['r180', 'flip_v', 'ccw']), 4], [(11, ['r180', 'cw', 'flip_v', 'cw']), 2]], [[(9, ['ccw', 'flip_h', 'flip_v']), 6], [(9, ['flip_h', 'flip_h', 'flip_h', 'cw']), 7], [(9, ['cw']), 6], [(4, ['ccw']), 7], [(8, ['rotate']), {'error': 'unknown-op', 'op': 'rotate'}], [(4, ['ccw', 'ccw', 'flip_v', 'ccw']), 6], [(1, ['r180', 'flip_h', 'cw', 'flip_v']), 8], [(0, ['r180', 'ccw', 'cw', 'cw']), 8]], [[(0, ['flip_v', 'cw']), 5], [(9, ['ccw', 'cw', 'flip_v']), 4], [(11, ['r180']), 3], [(1, []), 1], [(5, ['r180']), 7], [(6, ['ccw']), 1], [(7, ['CW', 'r180', 'r180', 'flip_h']), {'error': 'unknown-op', 'op': 'CW'}], [(11, ['cw', 'cw', 'ccw']), 6]]]
labels = ["regression: invalid starting 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: invalid starting tag 0 | 4 | 4 | Passed |
| repair trap 1 | 7 | 2 | Failed |
| combined fault 2 | 8 | 1 | Failed |
| control 3 | 7 | 7 | Passed |
| control 4 | 3 | 3 | Passed |
| boundary 5 | 8 | 8 | Passed |
| boundary 6 | {'error': 'unknown-op', 'op': 'rotate'} | {'error': 'unknown-op', 'op': 'rotate'} | Passed |
| control 7 | {'error': 'unknown-op', 'op': 'rotate'} | {'error': 'unknown-op', 'op': 'rotate'} | Passed |
SHA-256 / e7f6e4e7bf79540e2688cb32bf808c82bb882ab78969a20bd101d470ab53fb38
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(tag, ops):
to_km = {1: (0, 0), 2: (0, 1), 3: (2, 0), 4: (2, 1), 5: (3, 1), 6: (1, 0), 7: (1, 1), 8: (3, 0)}
from_km = {v: t for t, v in to_km.items()}
if tag not in to_km:
tag = 1
k, m = to_km[tag]
for op in ops:
if op == 'cw':
k = (k + 1) % 4
elif op == 'ccw':
k = (k + 3) % 4
elif op == 'r180':
k = (k + 2) % 4
elif op == 'flip_h':
k, m = (-k) % 4, 1 - m
elif op == 'flip_v':
k, m = (2 - k) % 4, 1 - m
else:
return {'error': 'unknown-op', 'op': op}
return from_km[(k, m)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[(0, ['flip_v']), 4], [(9, ['flip_h']), 2], [(9, []), 1], [(8, ['ccw', 'flip_h', 'ccw']), 7], [(7, ['flip_v', 'ccw', 'r180']), 3], [(8, ['ccw', 'cw']), 8], [(3, ['rotate']), {'error': 'unknown-op', 'op': 'rotate'}], [(0, ['cw', 'flip_v', 'rotate']), {'error': 'unknown-op', 'op': 'rotate'}]], [[(0, []), 1], [(9, ['cw', 'flip_v', 'flip_v']), 6], [(11, ['flip_v', 'ccw', 'flip_h', 'flip_v']), 5], [(8, ['ccw', 'r180']), 1], [(4, ['mirror', 'flip_v', 'cw', 'r180']), {'error': 'unknown-op', 'op': 'mirror'}], [(2, ['flip_v', 'ccw', 'cw']), 3], [(5, ['ccw', 'mirror']), {'error': 'unknown-op', 'op': 'mirror'}], [(11, ['r180']), 3]], [[(9, ['cw', 'r180', 'flip_h']), 7], [(11, ['cw', 'flip_v', 'flip_h']), 8], [(9, ['flip_v', 'flip_v', 'flip_v', 'cw']), 5], [(1, ['ccw', 'ccw', 'r180']), 1], [(5, ['flip_h']), 6], [(2, ['flip_v', 'cw', 'cw']), 1], [(6, ['r180', 'flip_v', 'ccw']), 4], [(11, ['r180', 'cw', 'flip_v', 'cw']), 2]], [[(9, ['ccw', 'flip_h', 'flip_v']), 6], [(9, ['flip_h', 'flip_h', 'flip_h', 'cw']), 7], [(9, ['cw']), 6], [(4, ['ccw']), 7], [(8, ['rotate']), {'error': 'unknown-op', 'op': 'rotate'}], [(4, ['ccw', 'ccw', 'flip_v', 'ccw']), 6], [(1, ['r180', 'flip_h', 'cw', 'flip_v']), 8], [(0, ['r180', 'ccw', 'cw', 'cw']), 8]], [[(0, ['flip_v', 'cw']), 5], [(9, ['ccw', 'cw', 'flip_v']), 4], [(11, ['r180']), 3], [(1, []), 1], [(5, ['r180']), 7], [(6, ['ccw']), 1], [(7, ['CW', 'r180', 'r180', 'flip_h']), {'error': 'unknown-op', 'op': 'CW'}], [(11, ['cw', 'cw', 'ccw']), 6]]]
labels = ["regression: invalid starting 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: invalid starting tag 0 | 4 | 4 | Passed |
| repair trap 1 | 2 | 2 | Passed |
| combined fault 2 | 1 | 1 | Passed |
| control 3 | 7 | 7 | Passed |
| control 4 | 3 | 3 | Passed |
| boundary 5 | 8 | 8 | Passed |
| boundary 6 | {'error': 'unknown-op', 'op': 'rotate'} | {'error': 'unknown-op', 'op': 'rotate'} | Passed |
| control 7 | {'error': 'unknown-op', 'op': 'rotate'} | {'error': 'unknown-op', 'op': 'rotate'} | Passed |
SHA-256 / 7d8ff30117e484be2083159e75882b82fdd95a61f69b84957d5949fa81507e6e
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:39.970507+00:00.
Case digest / 851136a4f637a3fe13ad607d1bd0d8a1d758608bcadef6cf02da7080afd1758c