FA-78971 / Image orientation metadata / Open access
Thumbnailer rotates the bounding box instead of the image · case 01
Portrait thumbnails come back with landscape dimensions for rotated photos.
ROOT CAUSE
For tags 5..8 the box dimensions are exchanged instead of the image dimensions, so the result is in stored orientation.
VERIFIED REPAIR
Swap the image dimensions into display orientation and fit them into the unmodified box.
Unsuccessful approach: Swapping both the image and the box fits the displayed image into the wrong box.
Case contract
Input [stored_w, stored_h, tag, box_w, box_h]. Compute the displayed thumbnail size: tags 5..8 swap the displayed dimensions (other values, including invalid tags, do not). Scale = min(box_w/dw, box_h/dh, 1) exactly (never upscale); each side is rounded half up and is at least 1 pixel.
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
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
w, h, tag, bw, bh = x
if tag in (5, 6, 7, 8):
bw, bh = bh, bw
s = min(Fraction(bw, w), Fraction(bh, h), Fraction(1))
tw = max(1, math.floor(w * s + Fraction(1, 2)))
th = max(1, math.floor(h * s + Fraction(1, 2)))
return [tw, th]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[25, 2, 6, 76, 343], [2, 25]], [[5, 1, 8, 10, 2], [1, 2]], [[15, 7, 6, 165, 2], [1, 2]], [[5, 2, 2, 10, 2], [5, 2]], [[3, 2, 3, 2, 33], [2, 1]], [[58, 7, 2, 2, 277], [2, 1]], [[15, 7, 1, 2, 10], [2, 1]], [[3, 1, 6, 10, 10], [1, 3]]], [[[17, 50, 7, 72, 27], [50, 17]], [[15, 35, 6, 31, 10], [23, 10]], [[5, 2, 1, 19, 25], [5, 2]], [[25, 7, 2, 2, 145], [2, 1]], [[15, 2, 0, 2, 2], [2, 1]], [[5, 4382, 0, 2, 337], [1, 337]], [[3, 7, 4, 2, 6], [2, 5]], [[4728, 23, 5, 14, 18], [1, 18]]], [[[15, 1, 8, 10, 42], [1, 15]], [[5, 34, 6, 2, 10], [2, 1]], [[12, 1369, 3, 1, 30], [1, 30]], [[15, 1, 1, 11, 291], [11, 1]], [[2, 1, 3, 2, 2], [2, 1]], [[15, 1, 3, 2, 10], [2, 1]], [[1669, 12, 4, 2, 2], [2, 1]], [[25, 7, 7, 10, 345], [7, 25]]], [[[5, 2, 8, 2, 10], [2, 5]], [[25, 1, 6, 10, 150], [1, 25]], [[5, 22, 5, 2, 17], [2, 1]], [[3, 5, 2, 2, 22], [2, 3]], [[1584, 7, 0, 266, 9], [266, 1]], [[51, 2, 3, 26, 45], [26, 1]], [[378, 2, 1, 2, 294], [2, 1]], [[1283, 2, 6, 10, 2], [1, 2]]], [[[2114, 2, 8, 15, 246], [1, 246]], [[25, 2, 6, 10, 2], [1, 2]], [[35, 33, 6, 10, 382], [10, 11]], [[3903, 7, 9, 364, 10], [364, 1]], [[25, 2, 4, 54, 2], [25, 2]], [[3018, 47, 4, 2, 10], [2, 1]], [[15, 1, 2, 14, 14], [14, 1]], [[3907, 2, 8, 28, 202], [1, 202]]]]
labels = ["regression: rotated image versus rotated box", "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: rotated image versus rotated box 0 | [25, 2] | [2, 25] | Failed |
| repair trap 1 | [2, 1] | [1, 2] | Failed |
| combined fault 2 | [2, 1] | [1, 2] | Failed |
| control 3 | [5, 2] | [5, 2] | Passed |
| control 4 | [2, 1] | [2, 1] | Passed |
| boundary 5 | [2, 1] | [2, 1] | Passed |
| boundary 6 | [2, 1] | [2, 1] | Passed |
| control 7 | [3, 1] | [1, 3] | Failed |
SHA-256 / 83b850ab76a184eafdf122402fb387ee9fb26d9b36a8d6e027f3f547cc3ad6ed
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
w, h, tag, bw, bh = x
if tag in (5, 6, 7, 8):
w, h = h, w
bw, bh = bh, bw
s = min(Fraction(bw, w), Fraction(bh, h), Fraction(1))
tw = max(1, math.floor(w * s + Fraction(1, 2)))
th = max(1, math.floor(h * s + Fraction(1, 2)))
return [tw, th]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[25, 2, 6, 76, 343], [2, 25]], [[5, 1, 8, 10, 2], [1, 2]], [[15, 7, 6, 165, 2], [1, 2]], [[5, 2, 2, 10, 2], [5, 2]], [[3, 2, 3, 2, 33], [2, 1]], [[58, 7, 2, 2, 277], [2, 1]], [[15, 7, 1, 2, 10], [2, 1]], [[3, 1, 6, 10, 10], [1, 3]]], [[[17, 50, 7, 72, 27], [50, 17]], [[15, 35, 6, 31, 10], [23, 10]], [[5, 2, 1, 19, 25], [5, 2]], [[25, 7, 2, 2, 145], [2, 1]], [[15, 2, 0, 2, 2], [2, 1]], [[5, 4382, 0, 2, 337], [1, 337]], [[3, 7, 4, 2, 6], [2, 5]], [[4728, 23, 5, 14, 18], [1, 18]]], [[[15, 1, 8, 10, 42], [1, 15]], [[5, 34, 6, 2, 10], [2, 1]], [[12, 1369, 3, 1, 30], [1, 30]], [[15, 1, 1, 11, 291], [11, 1]], [[2, 1, 3, 2, 2], [2, 1]], [[15, 1, 3, 2, 10], [2, 1]], [[1669, 12, 4, 2, 2], [2, 1]], [[25, 7, 7, 10, 345], [7, 25]]], [[[5, 2, 8, 2, 10], [2, 5]], [[25, 1, 6, 10, 150], [1, 25]], [[5, 22, 5, 2, 17], [2, 1]], [[3, 5, 2, 2, 22], [2, 3]], [[1584, 7, 0, 266, 9], [266, 1]], [[51, 2, 3, 26, 45], [26, 1]], [[378, 2, 1, 2, 294], [2, 1]], [[1283, 2, 6, 10, 2], [1, 2]]], [[[2114, 2, 8, 15, 246], [1, 246]], [[25, 2, 6, 10, 2], [1, 2]], [[35, 33, 6, 10, 382], [10, 11]], [[3903, 7, 9, 364, 10], [364, 1]], [[25, 2, 4, 54, 2], [25, 2]], [[3018, 47, 4, 2, 10], [2, 1]], [[15, 1, 2, 14, 14], [14, 1]], [[3907, 2, 8, 28, 202], [1, 202]]]]
labels = ["regression: rotated image versus rotated box", "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: rotated image versus rotated box 0 | [2, 25] | [2, 25] | Passed |
| repair trap 1 | [1, 5] | [1, 2] | Failed |
| combined fault 2 | [2, 4] | [1, 2] | Failed |
| control 3 | [5, 2] | [5, 2] | Passed |
| control 4 | [2, 1] | [2, 1] | Passed |
| boundary 5 | [2, 1] | [2, 1] | Passed |
| boundary 6 | [2, 1] | [2, 1] | Passed |
| control 7 | [1, 3] | [1, 3] | Passed |
SHA-256 / ef00e4d0c3debd053d5844f54034e44805c66164f7901395956c63f0eca55f13
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
w, h, tag, bw, bh = x
if tag in (5, 6, 7, 8):
w, h = h, w
s = min(Fraction(bw, w), Fraction(bh, h), Fraction(1))
tw = max(1, math.floor(w * s + Fraction(1, 2)))
th = max(1, math.floor(h * s + Fraction(1, 2)))
return [tw, th]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[[[25, 2, 6, 76, 343], [2, 25]], [[5, 1, 8, 10, 2], [1, 2]], [[15, 7, 6, 165, 2], [1, 2]], [[5, 2, 2, 10, 2], [5, 2]], [[3, 2, 3, 2, 33], [2, 1]], [[58, 7, 2, 2, 277], [2, 1]], [[15, 7, 1, 2, 10], [2, 1]], [[3, 1, 6, 10, 10], [1, 3]]], [[[17, 50, 7, 72, 27], [50, 17]], [[15, 35, 6, 31, 10], [23, 10]], [[5, 2, 1, 19, 25], [5, 2]], [[25, 7, 2, 2, 145], [2, 1]], [[15, 2, 0, 2, 2], [2, 1]], [[5, 4382, 0, 2, 337], [1, 337]], [[3, 7, 4, 2, 6], [2, 5]], [[4728, 23, 5, 14, 18], [1, 18]]], [[[15, 1, 8, 10, 42], [1, 15]], [[5, 34, 6, 2, 10], [2, 1]], [[12, 1369, 3, 1, 30], [1, 30]], [[15, 1, 1, 11, 291], [11, 1]], [[2, 1, 3, 2, 2], [2, 1]], [[15, 1, 3, 2, 10], [2, 1]], [[1669, 12, 4, 2, 2], [2, 1]], [[25, 7, 7, 10, 345], [7, 25]]], [[[5, 2, 8, 2, 10], [2, 5]], [[25, 1, 6, 10, 150], [1, 25]], [[5, 22, 5, 2, 17], [2, 1]], [[3, 5, 2, 2, 22], [2, 3]], [[1584, 7, 0, 266, 9], [266, 1]], [[51, 2, 3, 26, 45], [26, 1]], [[378, 2, 1, 2, 294], [2, 1]], [[1283, 2, 6, 10, 2], [1, 2]]], [[[2114, 2, 8, 15, 246], [1, 246]], [[25, 2, 6, 10, 2], [1, 2]], [[35, 33, 6, 10, 382], [10, 11]], [[3903, 7, 9, 364, 10], [364, 1]], [[25, 2, 4, 54, 2], [25, 2]], [[3018, 47, 4, 2, 10], [2, 1]], [[15, 1, 2, 14, 14], [14, 1]], [[3907, 2, 8, 28, 202], [1, 202]]]]
labels = ["regression: rotated image versus rotated box", "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: rotated image versus rotated box 0 | [2, 25] | [2, 25] | Passed |
| repair trap 1 | [1, 2] | [1, 2] | Passed |
| combined fault 2 | [1, 2] | [1, 2] | Passed |
| control 3 | [5, 2] | [5, 2] | Passed |
| control 4 | [2, 1] | [2, 1] | Passed |
| boundary 5 | [2, 1] | [2, 1] | Passed |
| boundary 6 | [2, 1] | [2, 1] | Passed |
| control 7 | [1, 3] | [1, 3] | Passed |
SHA-256 / 04ad3560b26b7ab62f82d0e7d177e8fe03e67b4779c8cc58027360e64736c431
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:40.266871+00:00.
Case digest / 2fb5a551b6e7b4c081a5e01260680b3f1c72904954ed5f718284d5d272cb14b2