FAILURE MAP
← Case archive

FA-79101 / Image orientation metadata / Open access

Back camera mount offset has the wrong sign · case 01

Every back-camera photo is tagged a half turn away from upright.

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

ROOT CAUSE

The 90 degree sensor mount is subtracted instead of added.

VERIFIED REPAIR

Add the 90 degree mount to the device rotation modulo 360.

Unsuccessful approach: Reducing modulo 180 folds 180 and 270 into the wrong tags.

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 = (r - 90) % 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 = [[[[-382, -382, 180, 'back'], [8, 180]], [[201, -186, 180, 'back'], [8, 180]], [[298, 553, 180, 'front'], [5, 0]], [[-285, -766, 90, 'front'], [7, 180]], [[439, -411, 180, 'front'], [4, 90]], [[686, -373, 90, 'front'], [4, 90]], [[-665, -531, 0, 'front'], [2, 270]], [[50, -890, 180, 'back'], [8, 180]]], [[[-926, 466, 0, 'back'], [1, 270]], [[110, -610, 0, 'back'], [8, 180]], [[-425, -823, 90, 'back'], [8, 180]], [[-835, 686, 0, 'front'], [2, 270]], [[-993, 66, 0, 'front'], [2, 270]], [[-242, 847, 270, 'front'], [5, 0]], [[465, -293, 180, 'front'], [4, 90]], [[-900, 900, 270, 'back'], [1, 270]]], [[[486, 992, 270, 'back'], [6, 0]], [[-134, -182, 180, 'back'], [8, 180]], [[161, 161, 180, 'back'], [8, 180]], [[-176, 109, 270, 'front'], [2, 270]], [[293, -866, 0, 'front'], [7, 180]], [[-160, -32, 0, 'front'], [5, 0]], [[558, 558, 0, 'front'], [5, 0]], [[741, -322, 270, 'back'], [3, 90]]], [[[137, 86, 180, 'back'], [8, 180]], [[-183, 181, 180, 'back'], [8, 180]], [[960, 530, 0, 'back'], [3, 90]], [[213, 206, 270, 'front'], [2, 270]], [[66, -283, 180, 'front'], [7, 180]], [[-522, 369, 0, 'front'], [2, 270]], [[207, -207, 270, 'front'], [2, 270]], [[230, 230, 270, 'back'], [1, 270]]], [[[816, 260, 0, 'back'], [3, 90]], [[-160, 69, 180, 'back'], [8, 180]], [[-624, -624, 90, 'back'], [3, 90]], [[-869, -869, 0, 'front'], [5, 0]], [[-48, -611, 90, 'front'], [7, 180]], [[6, -76, 270, 'front'], [2, 270]], [[105, 199, 180, 'front'], [7, 180]], [[-494, -320, 180, 'back'], [1, 270]]]]
labels = ["regression: back sensor mount offset", "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: back sensor mount offset 0[6, 180][8, 180]Failed
repair trap 1[6, 180][8, 180]Failed
combined fault 2[5, 0][5, 0]Passed
control 3[7, 180][7, 180]Passed
control 4[4, 90][4, 90]Passed
boundary 5[4, 90][4, 90]Passed
boundary 6[2, 270][2, 270]Passed
control 7[6, 180][8, 180]Failed

SHA-256 / 7bfd8e34bd426ff2b7fe3cb15bb6838c05a56dc50467a595a65f0aa13c5a8ed7

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 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) % 180
    return [from_km[(total // 90, 0)], r]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[-382, -382, 180, 'back'], [8, 180]], [[201, -186, 180, 'back'], [8, 180]], [[298, 553, 180, 'front'], [5, 0]], [[-285, -766, 90, 'front'], [7, 180]], [[439, -411, 180, 'front'], [4, 90]], [[686, -373, 90, 'front'], [4, 90]], [[-665, -531, 0, 'front'], [2, 270]], [[50, -890, 180, 'back'], [8, 180]]], [[[-926, 466, 0, 'back'], [1, 270]], [[110, -610, 0, 'back'], [8, 180]], [[-425, -823, 90, 'back'], [8, 180]], [[-835, 686, 0, 'front'], [2, 270]], [[-993, 66, 0, 'front'], [2, 270]], [[-242, 847, 270, 'front'], [5, 0]], [[465, -293, 180, 'front'], [4, 90]], [[-900, 900, 270, 'back'], [1, 270]]], [[[486, 992, 270, 'back'], [6, 0]], [[-134, -182, 180, 'back'], [8, 180]], [[161, 161, 180, 'back'], [8, 180]], [[-176, 109, 270, 'front'], [2, 270]], [[293, -866, 0, 'front'], [7, 180]], [[-160, -32, 0, 'front'], [5, 0]], [[558, 558, 0, 'front'], [5, 0]], [[741, -322, 270, 'back'], [3, 90]]], [[[137, 86, 180, 'back'], [8, 180]], [[-183, 181, 180, 'back'], [8, 180]], [[960, 530, 0, 'back'], [3, 90]], [[213, 206, 270, 'front'], [2, 270]], [[66, -283, 180, 'front'], [7, 180]], [[-522, 369, 0, 'front'], [2, 270]], [[207, -207, 270, 'front'], [2, 270]], [[230, 230, 270, 'back'], [1, 270]]], [[[816, 260, 0, 'back'], [3, 90]], [[-160, 69, 180, 'back'], [8, 180]], [[-624, -624, 90, 'back'], [3, 90]], [[-869, -869, 0, 'front'], [5, 0]], [[-48, -611, 90, 'front'], [7, 180]], [[6, -76, 270, 'front'], [2, 270]], [[105, 199, 180, 'front'], [7, 180]], [[-494, -320, 180, 'back'], [1, 270]]]]
labels = ["regression: back sensor mount offset", "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: back sensor mount offset 0[6, 180][8, 180]Failed
repair trap 1[6, 180][8, 180]Failed
combined fault 2[5, 0][5, 0]Passed
control 3[7, 180][7, 180]Passed
control 4[4, 90][4, 90]Passed
boundary 5[4, 90][4, 90]Passed
boundary 6[2, 270][2, 270]Passed
control 7[6, 180][8, 180]Failed

SHA-256 / 5af49ae944c0388b145175dfad07210a55f6a07758dbbace0bc4e183a05466ee

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 = [[[[-382, -382, 180, 'back'], [8, 180]], [[201, -186, 180, 'back'], [8, 180]], [[298, 553, 180, 'front'], [5, 0]], [[-285, -766, 90, 'front'], [7, 180]], [[439, -411, 180, 'front'], [4, 90]], [[686, -373, 90, 'front'], [4, 90]], [[-665, -531, 0, 'front'], [2, 270]], [[50, -890, 180, 'back'], [8, 180]]], [[[-926, 466, 0, 'back'], [1, 270]], [[110, -610, 0, 'back'], [8, 180]], [[-425, -823, 90, 'back'], [8, 180]], [[-835, 686, 0, 'front'], [2, 270]], [[-993, 66, 0, 'front'], [2, 270]], [[-242, 847, 270, 'front'], [5, 0]], [[465, -293, 180, 'front'], [4, 90]], [[-900, 900, 270, 'back'], [1, 270]]], [[[486, 992, 270, 'back'], [6, 0]], [[-134, -182, 180, 'back'], [8, 180]], [[161, 161, 180, 'back'], [8, 180]], [[-176, 109, 270, 'front'], [2, 270]], [[293, -866, 0, 'front'], [7, 180]], [[-160, -32, 0, 'front'], [5, 0]], [[558, 558, 0, 'front'], [5, 0]], [[741, -322, 270, 'back'], [3, 90]]], [[[137, 86, 180, 'back'], [8, 180]], [[-183, 181, 180, 'back'], [8, 180]], [[960, 530, 0, 'back'], [3, 90]], [[213, 206, 270, 'front'], [2, 270]], [[66, -283, 180, 'front'], [7, 180]], [[-522, 369, 0, 'front'], [2, 270]], [[207, -207, 270, 'front'], [2, 270]], [[230, 230, 270, 'back'], [1, 270]]], [[[816, 260, 0, 'back'], [3, 90]], [[-160, 69, 180, 'back'], [8, 180]], [[-624, -624, 90, 'back'], [3, 90]], [[-869, -869, 0, 'front'], [5, 0]], [[-48, -611, 90, 'front'], [7, 180]], [[6, -76, 270, 'front'], [2, 270]], [[105, 199, 180, 'front'], [7, 180]], [[-494, -320, 180, 'back'], [1, 270]]]]
labels = ["regression: back sensor mount offset", "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: back sensor mount offset 0[8, 180][8, 180]Passed
repair trap 1[8, 180][8, 180]Passed
combined fault 2[5, 0][5, 0]Passed
control 3[7, 180][7, 180]Passed
control 4[4, 90][4, 90]Passed
boundary 5[4, 90][4, 90]Passed
boundary 6[2, 270][2, 270]Passed
control 7[8, 180][8, 180]Passed

SHA-256 / 44ebab038fd62d926d46c71e093cbf84cca9b0d371bde65cff27799100abc911

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.244996+00:00.

Case digest / 4d39db9131be67d73dbd010324a08d23fdea0fb055abee20da27d2bbd6e5426b