FA-70256 / Map projection transforms / Open access
Affine geotransform inverse to pixel indices: determinant cross term · case 01
Rotated or sheared rasters are sampled at the wrong pixels.
ROOT CAUSE
The determinant adds the rotation product instead of subtracting it.
VERIFIED REPAIR
At the determinant cross term step restore `det = gt[1] * gt[5] - gt[2] * gt[4]`, leaving the rest of the model unchanged.
Unsuccessful approach: Dropping the rotation product is correct only for north-up rasters.
Case contract
Input [gx, gy, gt, width, height] with a six-term affine gt: X = gt0 + col*gt1 + row*gt2, Y = gt3 + col*gt4 + row*gt5, where (gt0, gt3) is the outer corner of pixel (0, 0). Invert with the 2x2 determinant, take floor of the fractional column and row, and return [col, row] if inside [0, width) x [0, height), otherwise None (also None for a singular transform).
Why this case matters
Point sampling of rasters and click-to-pixel lookups rely on this inverse; half-pixel or sign mistakes read the neighbouring cell.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
gx, gy, gt, width, height = x
det = gt[1] * gt[5] + gt[2] * gt[4]
if det == 0:
return None
dx = gx - gt[0]
dy = gy - gt[3]
col = (gt[5] * dx - gt[2] * dy) / det
row = (-gt[4] * dx + gt[1] * dy) / det
c, r = math.floor(col), math.floor(row)
if c < 0 or r < 0 or c >= width or r >= height:
return None
return [c, r]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [501757.3953, 4199818.2948, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [58, 6]), ('control #1', [-171.8788, 83.4562, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [32, 26]), ('control #2', [1046.5787, 4959.4742, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [17, 23]), ('control #3', [515.0875, 607.7306, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [32, 35]), ('control #4', [76.2652, 18.2363, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [76, 18]), ('control #5', [500628.6941, 4200027.3496, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], None), ('control #6', [-155.5787, 74.1746, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [97, 63]), ('control #7', [1081.9041, 4971.5191, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [35, 21])], [('control #3', [515.0875, 607.7306, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [32, 35]), ('control #4', [76.2652, 18.2363, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [76, 18]), ('control #5', [500628.6941, 4200027.3496, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], None), ('control #6', [-155.5787, 74.1746, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [97, 63]), ('control #7', [1081.9041, 4971.5191, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [35, 21]), ('control #8', [1154.1901, 783.547, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [94, 30]), ('control #9', [38.71, 51.2013, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [38, 51]), ('control #10', [500787.7801, 4199579.3798, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [26, 14])], [('control #6', [-155.5787, 74.1746, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [97, 63]), ('control #7', [1081.9041, 4971.5191, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [35, 21]), ('control #8', [1154.1901, 783.547, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [94, 30]), ('control #9', [38.71, 51.2013, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [38, 51]), ('control #10', [500787.7801, 4199579.3798, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [26, 14]), ('control #11', [-179.8182, 72.2686, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [0, 70]), ('control #12', [1181.1787, 4910.1448, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [75, 60]), ('control #13', [651.582, 912.3698, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [37, 6])], [('control #8', [1154.1901, 783.547, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [94, 30]), ('control #10', [500787.7801, 4199579.3798, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [26, 14]), ('control #11', [-179.8182, 72.2686, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [0, 70]), ('control #12', [1181.1787, 4910.1448, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [75, 60]), ('control #13', [651.582, 912.3698, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [37, 6]), ('control #14', [43.3467, 82.4576, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], None), ('control #15', [500583.8568, 4199766.5953, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [19, 7]), ('control #16', [-155.0962, 71.5551, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [99, 73])], [('control #12', [1181.1787, 4910.1448, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [75, 60]), ('control #13', [651.582, 912.3698, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [37, 6]), ('control #14', [43.3467, 82.4576, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], None), ('control #15', [500583.8568, 4199766.5953, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [19, 7]), ('control #16', [-155.0962, 71.5551, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [99, 73]), ('control #17', [1052.7736, 4892.5715, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [12, 56]), ('control #18', [1072.1342, 274.3213, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], None), ('control #19', [85.7339, 56.5251, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [85, 56])]]
for label, args, expected in fixtures[N-1]:
check(label, 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 |
|---|---|---|---|
| control #0 | [58, 6] | [58, 6] | Passed |
| control #1 | [32, 26] | [32, 26] | Passed |
| control #2 | [19, 26] | [17, 23] | Failed |
| control #3 | [28, 31] | [32, 35] | Failed |
| control #4 | [76, 18] | [76, 18] | Passed |
| control #5 | None | None | Passed |
| control #6 | [97, 63] | [97, 63] | Passed |
| control #7 | [39, 23] | [35, 21] | Failed |
SHA-256 / 94dfcf383ce3091fc6c065873820ded10d6df0aa4cd2beda39f1458080093eb9
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
gx, gy, gt, width, height = x
det = gt[1] * gt[5]
if det == 0:
return None
dx = gx - gt[0]
dy = gy - gt[3]
col = (gt[5] * dx - gt[2] * dy) / det
row = (-gt[4] * dx + gt[1] * dy) / det
c, r = math.floor(col), math.floor(row)
if c < 0 or r < 0 or c >= width or r >= height:
return None
return [c, r]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [501757.3953, 4199818.2948, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [58, 6]), ('control #1', [-171.8788, 83.4562, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [32, 26]), ('control #2', [1046.5787, 4959.4742, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [17, 23]), ('control #3', [515.0875, 607.7306, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [32, 35]), ('control #4', [76.2652, 18.2363, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [76, 18]), ('control #5', [500628.6941, 4200027.3496, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], None), ('control #6', [-155.5787, 74.1746, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [97, 63]), ('control #7', [1081.9041, 4971.5191, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [35, 21])], [('control #3', [515.0875, 607.7306, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [32, 35]), ('control #4', [76.2652, 18.2363, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [76, 18]), ('control #5', [500628.6941, 4200027.3496, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], None), ('control #6', [-155.5787, 74.1746, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [97, 63]), ('control #7', [1081.9041, 4971.5191, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [35, 21]), ('control #8', [1154.1901, 783.547, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [94, 30]), ('control #9', [38.71, 51.2013, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [38, 51]), ('control #10', [500787.7801, 4199579.3798, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [26, 14])], [('control #6', [-155.5787, 74.1746, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [97, 63]), ('control #7', [1081.9041, 4971.5191, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [35, 21]), ('control #8', [1154.1901, 783.547, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [94, 30]), ('control #9', [38.71, 51.2013, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [38, 51]), ('control #10', [500787.7801, 4199579.3798, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [26, 14]), ('control #11', [-179.8182, 72.2686, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [0, 70]), ('control #12', [1181.1787, 4910.1448, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [75, 60]), ('control #13', [651.582, 912.3698, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [37, 6])], [('control #8', [1154.1901, 783.547, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [94, 30]), ('control #10', [500787.7801, 4199579.3798, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [26, 14]), ('control #11', [-179.8182, 72.2686, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [0, 70]), ('control #12', [1181.1787, 4910.1448, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [75, 60]), ('control #13', [651.582, 912.3698, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [37, 6]), ('control #14', [43.3467, 82.4576, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], None), ('control #15', [500583.8568, 4199766.5953, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [19, 7]), ('control #16', [-155.0962, 71.5551, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [99, 73])], [('control #12', [1181.1787, 4910.1448, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [75, 60]), ('control #13', [651.582, 912.3698, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [37, 6]), ('control #14', [43.3467, 82.4576, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], None), ('control #15', [500583.8568, 4199766.5953, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [19, 7]), ('control #16', [-155.0962, 71.5551, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [99, 73]), ('control #17', [1052.7736, 4892.5715, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [12, 56]), ('control #18', [1072.1342, 274.3213, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], None), ('control #19', [85.7339, 56.5251, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [85, 56])]]
for label, args, expected in fixtures[N-1]:
check(label, 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 |
|---|---|---|---|
| control #0 | [58, 6] | [58, 6] | Passed |
| control #1 | [32, 26] | [32, 26] | Passed |
| control #2 | [18, 24] | [17, 23] | Failed |
| control #3 | [30, 33] | [32, 35] | Failed |
| control #4 | [76, 18] | [76, 18] | Passed |
| control #5 | None | None | Passed |
| control #6 | [97, 63] | [97, 63] | Passed |
| control #7 | [37, 22] | [35, 21] | Failed |
SHA-256 / d99c1b613fcf71ea8b03a6c05ec6e5184851a53b82b38b2b650ff3596674ae50
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(x):
gx, gy, gt, width, height = x
det = gt[1] * gt[5] - gt[2] * gt[4]
if det == 0:
return None
dx = gx - gt[0]
dy = gy - gt[3]
col = (gt[5] * dx - gt[2] * dy) / det
row = (-gt[4] * dx + gt[1] * dy) / det
c, r = math.floor(col), math.floor(row)
if c < 0 or r < 0 or c >= width or r >= height:
return None
return [c, r]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('control #0', [501757.3953, 4199818.2948, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [58, 6]), ('control #1', [-171.8788, 83.4562, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [32, 26]), ('control #2', [1046.5787, 4959.4742, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [17, 23]), ('control #3', [515.0875, 607.7306, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [32, 35]), ('control #4', [76.2652, 18.2363, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [76, 18]), ('control #5', [500628.6941, 4200027.3496, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], None), ('control #6', [-155.5787, 74.1746, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [97, 63]), ('control #7', [1081.9041, 4971.5191, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [35, 21])], [('control #3', [515.0875, 607.7306, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [32, 35]), ('control #4', [76.2652, 18.2363, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [76, 18]), ('control #5', [500628.6941, 4200027.3496, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], None), ('control #6', [-155.5787, 74.1746, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [97, 63]), ('control #7', [1081.9041, 4971.5191, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [35, 21]), ('control #8', [1154.1901, 783.547, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [94, 30]), ('control #9', [38.71, 51.2013, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [38, 51]), ('control #10', [500787.7801, 4199579.3798, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [26, 14])], [('control #6', [-155.5787, 74.1746, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [97, 63]), ('control #7', [1081.9041, 4971.5191, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [35, 21]), ('control #8', [1154.1901, 783.547, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [94, 30]), ('control #9', [38.71, 51.2013, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [38, 51]), ('control #10', [500787.7801, 4199579.3798, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [26, 14]), ('control #11', [-179.8182, 72.2686, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [0, 70]), ('control #12', [1181.1787, 4910.1448, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [75, 60]), ('control #13', [651.582, 912.3698, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [37, 6])], [('control #8', [1154.1901, 783.547, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [94, 30]), ('control #10', [500787.7801, 4199579.3798, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [26, 14]), ('control #11', [-179.8182, 72.2686, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [0, 70]), ('control #12', [1181.1787, 4910.1448, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [75, 60]), ('control #13', [651.582, 912.3698, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [37, 6]), ('control #14', [43.3467, 82.4576, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], None), ('control #15', [500583.8568, 4199766.5953, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [19, 7]), ('control #16', [-155.0962, 71.5551, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [99, 73])], [('control #12', [1181.1787, 4910.1448, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [75, 60]), ('control #13', [651.582, 912.3698, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], [37, 6]), ('control #14', [43.3467, 82.4576, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], None), ('control #15', [500583.8568, 4199766.5953, [500000.0, 30.0, 0.0, 4200000.0, 0.0, -30.0], 100, 80], [19, 7]), ('control #16', [-155.0962, 71.5551, [-180.0, 0.25, 0.0, 90.0, 0.0, -0.25], 100, 80], [99, 73]), ('control #17', [1052.7736, 4892.5715, [1000.0, 2.0, 0.5, 5000.0, 0.4, -2.0], 100, 80], [12, 56]), ('control #18', [1072.1342, 274.3213, [300.0, 10.0, -3.0, 900.0, 2.0, -10.0], 100, 80], None), ('control #19', [85.7339, 56.5251, [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], 100, 80], [85, 56])]]
for label, args, expected in fixtures[N-1]:
check(label, 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 |
|---|---|---|---|
| control #0 | [58, 6] | [58, 6] | Passed |
| control #1 | [32, 26] | [32, 26] | Passed |
| control #2 | [17, 23] | [17, 23] | Passed |
| control #3 | [32, 35] | [32, 35] | Passed |
| control #4 | [76, 18] | [76, 18] | Passed |
| control #5 | None | None | Passed |
| control #6 | [97, 63] | [97, 63] | Passed |
| control #7 | [35, 21] | [35, 21] | Passed |
SHA-256 / d3ef3a78ac263d15d6ca7b7e91ee69522826b07b867c9b47dfb5e4b546a2d1ad
Verification & scope
Stipulated deterministic toy contract on a bounded input domain; results are rounded as stated and no conformance with any published standard or library is claimed. 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:48:19.078600+00:00.
Case digest / 0da5a12b6f67245eda3e10cb6708401a1070f701d32ce8318e6008ddcf0b3874