FA-79091 / Image orientation metadata / Open access
Landscape captures are tagged with the opposite turn · case 01
Landscape photos taken with the phone turned clockwise display upside down.
ROOT CAUSE
Gravity along +x means the device was turned clockwise, but the code maps it to 270.
VERIFIED REPAIR
Map gx > 0 to 90 and gx < 0 to 270.
Unsuccessful approach: Using the sign of gy in the landscape branch reads the minor axis.
Case contract
Input [gx, gy, previous_rotation, camera] with gravity in milli-g in the portrait device frame (x right, y down). Device rotation: keep the previous bucket when the in-plane magnitude is below 300 mg or |gx| == |gy| (hysteresis); otherwise gy dominates -> 0 (gy > 0) or 180, gx dominates -> 90 (gx > 0) or 270. The back sensor is mounted at 90 degrees and needs (90 + r) mod 360 clockwise; the mirrored front sensor is mounted at 270 and needs (270 - r) mod 360 with the mirror bit. The tag comes from (quarter turns, mirror) via 1:(0,0) 2:(0,1) 3:(2,0) 4:(2,1) 5:(3,1) 6:(1,0) 7:(1,1) 8:(3,0). Return [tag, r].
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(x):
gx, gy, prev_r, camera = x
if gx * gx + gy * gy < 300 * 300 or abs(gx) == abs(gy):
r = prev_r
elif abs(gy) > abs(gx):
r = 0 if gy > 0 else 180
else:
r = 90 if gx < 0 else 270
from_km = {(0, 0): 1, (0, 1): 2, (2, 0): 3, (2, 1): 4, (3, 1): 5, (1, 0): 6, (1, 1): 7, (3, 0): 8}
if camera == 'front':
total = (270 - r) % 360
return [from_km[(total // 90, 1)], r]
total = (90 + r) % 360
return [from_km[(total // 90, 0)], r]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[439, -411, 180, 'front'], [4, 90]], [[-797, 786, 180, 'back'], [1, 270]], [[-382, -382, 180, 'back'], [8, 180]], [[50, -890, 180, 'back'], [8, 180]], [[298, 553, 180, 'front'], [5, 0]], [[53, -848, 270, 'back'], [8, 180]], [[247, -67, 270, 'back'], [1, 270]], [[686, -373, 90, 'front'], [4, 90]]], [[[-869, 635, 270, 'back'], [1, 270]], [[465, -293, 180, 'front'], [4, 90]], [[184, 153, 90, 'back'], [3, 90]], [[110, -610, 0, 'back'], [8, 180]], [[-900, 900, 270, 'back'], [1, 270]], [[-239, -597, 180, 'back'], [8, 180]], [[-64, 236, 90, 'back'], [3, 90]], [[-869, -556, 180, 'back'], [1, 270]]], [[[734, 301, 180, 'back'], [3, 90]], [[-994, 954, 180, 'front'], [2, 270]], [[856, -211, 0, 'front'], [4, 90]], [[54, 63, 0, 'back'], [6, 0]], [[-86, -105, 270, 'back'], [1, 270]], [[-134, -182, 180, 'back'], [8, 180]], [[60, 809, 0, 'front'], [5, 0]], [[741, -322, 270, 'back'], [3, 90]]], [[[915, 611, 180, 'back'], [3, 90]], [[764, -669, 90, 'front'], [4, 90]], [[-978, 833, 0, 'back'], [1, 270]], [[-899, 899, 90, 'front'], [4, 90]], [[230, 230, 270, 'back'], [1, 270]], [[175, -26, 270, 'back'], [1, 270]], [[213, 206, 270, 'front'], [2, 270]], [[-522, 369, 0, 'front'], [2, 270]]], [[[994, 659, 180, 'front'], [4, 90]], [[-933, 88, 90, 'back'], [1, 270]], [[971, -381, 180, 'back'], [3, 90]], [[-22, 28, 0, 'front'], [5, 0]], [[-52, -248, 270, 'front'], [2, 270]], [[-869, -869, 0, 'front'], [5, 0]], [[-67, 67, 270, 'back'], [1, 270]], [[-494, -320, 180, 'back'], [1, 270]]]]
labels = ["regression: landscape direction sign", "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: landscape direction sign 0 | [2, 270] | [4, 90] | Failed |
| repair trap 1 | [3, 90] | [1, 270] | Failed |
| combined fault 2 | [8, 180] | [8, 180] | Passed |
| control 3 | [8, 180] | [8, 180] | Passed |
| control 4 | [5, 0] | [5, 0] | Passed |
| boundary 5 | [8, 180] | [8, 180] | Passed |
| boundary 6 | [1, 270] | [1, 270] | Passed |
| control 7 | [2, 270] | [4, 90] | Failed |
SHA-256 / 9e743cb80f8f8b130adb1687758fa94c1d43c48598bca30fcf53065da0f58606
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
gx, gy, prev_r, camera = x
if gx * gx + gy * gy < 300 * 300 or abs(gx) == abs(gy):
r = prev_r
elif abs(gy) > abs(gx):
r = 0 if gy > 0 else 180
else:
r = 90 if gy > 0 else 270
from_km = {(0, 0): 1, (0, 1): 2, (2, 0): 3, (2, 1): 4, (3, 1): 5, (1, 0): 6, (1, 1): 7, (3, 0): 8}
if camera == 'front':
total = (270 - r) % 360
return [from_km[(total // 90, 1)], r]
total = (90 + r) % 360
return [from_km[(total // 90, 0)], r]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[439, -411, 180, 'front'], [4, 90]], [[-797, 786, 180, 'back'], [1, 270]], [[-382, -382, 180, 'back'], [8, 180]], [[50, -890, 180, 'back'], [8, 180]], [[298, 553, 180, 'front'], [5, 0]], [[53, -848, 270, 'back'], [8, 180]], [[247, -67, 270, 'back'], [1, 270]], [[686, -373, 90, 'front'], [4, 90]]], [[[-869, 635, 270, 'back'], [1, 270]], [[465, -293, 180, 'front'], [4, 90]], [[184, 153, 90, 'back'], [3, 90]], [[110, -610, 0, 'back'], [8, 180]], [[-900, 900, 270, 'back'], [1, 270]], [[-239, -597, 180, 'back'], [8, 180]], [[-64, 236, 90, 'back'], [3, 90]], [[-869, -556, 180, 'back'], [1, 270]]], [[[734, 301, 180, 'back'], [3, 90]], [[-994, 954, 180, 'front'], [2, 270]], [[856, -211, 0, 'front'], [4, 90]], [[54, 63, 0, 'back'], [6, 0]], [[-86, -105, 270, 'back'], [1, 270]], [[-134, -182, 180, 'back'], [8, 180]], [[60, 809, 0, 'front'], [5, 0]], [[741, -322, 270, 'back'], [3, 90]]], [[[915, 611, 180, 'back'], [3, 90]], [[764, -669, 90, 'front'], [4, 90]], [[-978, 833, 0, 'back'], [1, 270]], [[-899, 899, 90, 'front'], [4, 90]], [[230, 230, 270, 'back'], [1, 270]], [[175, -26, 270, 'back'], [1, 270]], [[213, 206, 270, 'front'], [2, 270]], [[-522, 369, 0, 'front'], [2, 270]]], [[[994, 659, 180, 'front'], [4, 90]], [[-933, 88, 90, 'back'], [1, 270]], [[971, -381, 180, 'back'], [3, 90]], [[-22, 28, 0, 'front'], [5, 0]], [[-52, -248, 270, 'front'], [2, 270]], [[-869, -869, 0, 'front'], [5, 0]], [[-67, 67, 270, 'back'], [1, 270]], [[-494, -320, 180, 'back'], [1, 270]]]]
labels = ["regression: landscape direction sign", "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: landscape direction sign 0 | [2, 270] | [4, 90] | Failed |
| repair trap 1 | [3, 90] | [1, 270] | Failed |
| combined fault 2 | [8, 180] | [8, 180] | Passed |
| control 3 | [8, 180] | [8, 180] | Passed |
| control 4 | [5, 0] | [5, 0] | Passed |
| boundary 5 | [8, 180] | [8, 180] | Passed |
| boundary 6 | [1, 270] | [1, 270] | Passed |
| control 7 | [2, 270] | [4, 90] | Failed |
SHA-256 / a8c57a4b227281a0eaabfde4582a17d74b54226fb95a0becd367b2f86fabfc0e
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
gx, gy, prev_r, camera = x
if gx * gx + gy * gy < 300 * 300 or abs(gx) == abs(gy):
r = prev_r
elif abs(gy) > abs(gx):
r = 0 if gy > 0 else 180
else:
r = 90 if gx > 0 else 270
from_km = {(0, 0): 1, (0, 1): 2, (2, 0): 3, (2, 1): 4, (3, 1): 5, (1, 0): 6, (1, 1): 7, (3, 0): 8}
if camera == 'front':
total = (270 - r) % 360
return [from_km[(total // 90, 1)], r]
total = (90 + r) % 360
return [from_km[(total // 90, 0)], r]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[439, -411, 180, 'front'], [4, 90]], [[-797, 786, 180, 'back'], [1, 270]], [[-382, -382, 180, 'back'], [8, 180]], [[50, -890, 180, 'back'], [8, 180]], [[298, 553, 180, 'front'], [5, 0]], [[53, -848, 270, 'back'], [8, 180]], [[247, -67, 270, 'back'], [1, 270]], [[686, -373, 90, 'front'], [4, 90]]], [[[-869, 635, 270, 'back'], [1, 270]], [[465, -293, 180, 'front'], [4, 90]], [[184, 153, 90, 'back'], [3, 90]], [[110, -610, 0, 'back'], [8, 180]], [[-900, 900, 270, 'back'], [1, 270]], [[-239, -597, 180, 'back'], [8, 180]], [[-64, 236, 90, 'back'], [3, 90]], [[-869, -556, 180, 'back'], [1, 270]]], [[[734, 301, 180, 'back'], [3, 90]], [[-994, 954, 180, 'front'], [2, 270]], [[856, -211, 0, 'front'], [4, 90]], [[54, 63, 0, 'back'], [6, 0]], [[-86, -105, 270, 'back'], [1, 270]], [[-134, -182, 180, 'back'], [8, 180]], [[60, 809, 0, 'front'], [5, 0]], [[741, -322, 270, 'back'], [3, 90]]], [[[915, 611, 180, 'back'], [3, 90]], [[764, -669, 90, 'front'], [4, 90]], [[-978, 833, 0, 'back'], [1, 270]], [[-899, 899, 90, 'front'], [4, 90]], [[230, 230, 270, 'back'], [1, 270]], [[175, -26, 270, 'back'], [1, 270]], [[213, 206, 270, 'front'], [2, 270]], [[-522, 369, 0, 'front'], [2, 270]]], [[[994, 659, 180, 'front'], [4, 90]], [[-933, 88, 90, 'back'], [1, 270]], [[971, -381, 180, 'back'], [3, 90]], [[-22, 28, 0, 'front'], [5, 0]], [[-52, -248, 270, 'front'], [2, 270]], [[-869, -869, 0, 'front'], [5, 0]], [[-67, 67, 270, 'back'], [1, 270]], [[-494, -320, 180, 'back'], [1, 270]]]]
labels = ["regression: landscape direction sign", "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: landscape direction sign 0 | [4, 90] | [4, 90] | Passed |
| repair trap 1 | [1, 270] | [1, 270] | Passed |
| combined fault 2 | [8, 180] | [8, 180] | Passed |
| control 3 | [8, 180] | [8, 180] | Passed |
| control 4 | [5, 0] | [5, 0] | Passed |
| boundary 5 | [8, 180] | [8, 180] | Passed |
| boundary 6 | [1, 270] | [1, 270] | Passed |
| control 7 | [4, 90] | [4, 90] | Passed |
SHA-256 / 4cfa30b693cb0dae768fb2c59da3457abb9ef528b81e2ce20371fc6afc2cabfa
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:41.202974+00:00.
Case digest / 799eb057963ad90247c569cba73d9c3bcbc1854d0794cc7cdcdee5faa561112a