Artificial Wasteland · groundtruth
The Edge That Came Back Sharper
Photograph a straight edge tilted a few degrees and this page hands back your own imaging chain's spatial frequency response: the frequency at which it has lost half the contrast, in cycles per pixel of the frame you were given, and in cycles per millimetre of paper once you have printed the target and measured the printed ruler with a ruler of your own. Before it will report anything about you, the same estimator has to recover four blurs whose true answers are closed forms and refuse six specimens it is supposed to refuse. A response that peaks above 1.000 is arithmetic no lens performs. If yours does, the page shows you the bands the sharpener left beside the edge in your own picture.
Before it measures you, it measures something it cannot argue with
Four synthetic edges are drawn in your browser right now. Each one is an ideal step blurred by a known point spread function and then integrated over each pixel's square footprint, sixteen by sixteen sub-samples per pixel, and written out as ordinary 8-bit sRGB. Their true response is a closed form that the code drawing them never uses and the code measuring them never sees:
MTF(f) = optical(f) × sinc(f cos θ) × sinc(f sin θ), optical(f) = e−2π2σ2f2, f in cycles per pixel
The second and third factors are the pixel itself: a square detector of width w has a sinc transfer with its first zero at 1/w, which for a one-pixel-wide detector is one cycle per pixel. That term is the reason this anchor is a test and not a tautology. For the first specimen the blur alone crosses half at … cycles per pixel, and the blur times the pixel crosses at …. Those two candidate answers are … per cent apart, and an estimator that quietly ignored the detector footprint would land on the wrong one. The estimator landed on ….
The slate: cycles per pixel
running…
Tolerance is 2 per cent of each true value. The instrument stays disarmed until every line passes, and while it is disarmed it refuses to report a measurement of your camera at all. The region measured is 128 by 128 pixels in every case, including yours.
The slate: the peak of the response
running…
A second instrument on the same pixels, reading a different number off the same call. Its specimens are sharpened edges whose true peak is known in closed form. An unsharpened edge is deliberately not on this slate: its peak is 1.000 because the curve is normalised to 1.000 at zero frequency and then falls, so it is true by construction and could not fail.
What a fake would do
The same two slates, run through an estimator that ignores its input and always returns the anchor's value with a tight error bar. It sails through both anchors. It cannot pass a control.
…
…
The specimens your browser drew, against the ones in the repository
…
No image file is downloaded, so no image file can be doctored. What is committed is the sha256 of the exact 8-bit pixels the generator produces from each set of parameters. Your browser draws them and hashes them here; the verifier regenerates the same manifest in Node and fails on any difference.
Now your camera
Find a straight edge and tilt it two to fifteen degrees off vertical. A door frame against a wall, the side of a book, a printed line, the sheet this page can print for you further down. Fill the yellow square with the edge and press. Nothing leaves your device: the frame is held in memory for the moment the arithmetic takes and is never uploaded, stored or sent anywhere.
Which of those two you use changes the answer, and the second one is where the interesting effect lives. A live video track hands most phones a downscaled preview rather than the sensor, and a box or bilinear downscaler does not overshoot, so the live route often shows no peak above 1.000 at all. A still taken with the phone's own camera app goes through the still-capture pipeline, which is the one that sharpens. The page prints the delivered size for whichever you choose, so you can see which you got.
Clicking the picture does:
not measured
Your response curve
The square that was measured
The number that cannot be a lens
The obvious dismissal of everything above is: fine, you have measured my phone's optics. You have not, and the page can prove it from your own frame rather than assert it.
For any passive incoherent optical system the optical transfer function is the normalised autocorrelation of the pupil function. An autocorrelation is largest at zero shift, so the transfer is 1.000 at zero spatial frequency and can never exceed it anywhere. ISO 12233 states half of that in its own terms clause, the value at zero frequency, which is a normalisation and not a bound; the bound above it is the autocorrelation's and not the standard's. The clause defines the optical transfer function as the two-dimensional Fourier transform of the point spread function and notes of it:
“The OTF is a complex function whose modulus has unity value at zero spatial frequency”
ISO 12233:2024, 3.20, Note 1 to entry
So a response above 1.000 is not a good lens or a bad one. It is arithmetic that no passive optical system performs. When your phone hands one back, something after the optics has added contrast at those spatial frequencies, and it added contrast that was never on the paper.
Measure something above and this line will say what your own chain did.
The halo you can go and look at
Overshoot is not only a number. A sharpener that lifts the response above unity does it by putting a bright band on the light side of every edge and a dark band on the dark side, and those bands are in your photograph while being demonstrably absent from the thing you photographed. Here is the edge profile of the shipped specimen at the strongest sharpening on the table below, whose two true levels are known exactly, overshooting …:
And in your own frame
Measure something above and your own edge profile appears here.
Watch the headline number be manufactured
MTF50, the frequency at which contrast has halved, is the number most sharpness tests quote. It is also the number sharpening inflates hardest, because sharpening raises the curve near the middle and drags the half-height crossing outward with it. MTF50P, the crossing of half the curve's own peak, moves far less, because the peak it is measured against went up too. The table is the same optical blur with a difference of Gaussians on top at six gains, every row through the same estimator:
| gain | true peak | measured | true MTF50 | measured | inflation | MTF50P | inflation |
|---|---|---|---|---|---|---|---|
| computing… | |||||||
At the strongest sharpening on the table the headline number is inflated … times while the peak-relative one moves only … times, and the peak itself reaches …. Nothing optical changed between the first row and the last. If your own peak is above 1.000, the same thing has happened to your headline number, by an amount this page cannot separate out for you, because it never saw the frame before the pipeline did.
And what a peak of 1.000 does not rule out
That instrument points one way only, and the page had better be the one to say so. A peak above 1.000 is proof that something after the optics added contrast, because nothing before them can. A peak at 1.000 is not proof that nothing did, and the reason is arithmetic rather than modesty: the response is normalised to 1.000 at zero frequency, so 1.000 is the floor of this statistic, not a measurement that came out low. Whether a sharpened curve ever climbs back above that floor depends on how tight the sharpener is against how much blur it is fighting. Expand both transfers about zero frequency and the curve leaves 1.000 upward only when gσB2 − (1 + g)σ2 > 1/12, so a narrow sharpener can be run very hard with the peak sitting exactly on its floor the whole way.
Every row below sits at or under that threshold, so its true peak is exactly 1.000 in closed form. The estimator reads exactly 1.000 off all of them. The headline number moves anyway:
| sharpener | true peak | measured peak | true MTF50 | measured | inflation | MTF50P | inflation |
|---|---|---|---|---|---|---|---|
| computing… | |||||||
On the last row the true peak is … and the headline number has still been inflated … times. MTF50P does not rescue this. It is the crossing of half the curve's own peak, so when the peak is 1.000 it is the crossing of 0.500, which is MTF50: on … rows above the two numbers are equal to every digit the estimator computes, and the pair moves together. The statistic that resists sharpening only starts resisting once the peak has noticed something, and this is the band where the peak has not.
So the reading to take from your own frame is one-directional, and the line below your own number says so. Above 1.000: something was added, and this page will show you the bands. At 1.000: the edge method saw nothing, which is a different sentence from nothing being there, and the table above is how much can fit in the difference. That is a limit of this method rather than of this page, and the way out of it is a second method measuring the same chain, which is not something one page and one photograph can do.
Cycles per millimetre, or an honest refusal
Cycles per pixel is a statement about the file. Cycles per millimetre is a statement about the paper, and to get there this page needs to know how many pixels of your picture cover a known length of the world. That is what the printable sheet is for, and it is where the whole thing can quietly go wrong.
Nearly every printer driver defaults to fit-to-page, and fit-to-page is not one hundred per cent. Two separate causes land in the same place, and both are arithmetic rather than folklore, computed here from the paper sizes the shared kit holds: an A4-sized page sent to a US Letter printer is scaled by min(215.9 / 210, 279.4 / 297) = … per cent, and a page fitted inside the 4 to 6 mm all round that a consumer printer cannot reach is scaled by … to … per cent. Neither is announced. A sheet that claims to carry marks 150.000 mm apart and actually carries them 142 mm apart would not look wrong, and would make every millimetre on this page wrong by four per cent in a direction nobody could see. So the sheet carries a 150 mm ruler, and until you have measured it with a ruler of your own and typed in what you got, this page refuses to convert anything into millimetres. That refusal is the point of the whole exercise, not an inconvenience in front of it.
The sheet is built by the Wasteland's shared printable kit, so it carries the same four registration fiducials as every other printable page here: 4 mm squares whose centres sit at 20 and 170 mm across and 15 and 215 mm down from the top-left corner, a span of 150.000 mm. Nothing on it goes near the sheet edge, because a consumer printer generally cannot reach the last four to six millimetres all round, and borderless photo printing is the exception rather than the office default. The slanted patch adds its own pair of marks 40.000 mm apart, close to the edge itself, so the scale still works when you move in close enough that the sheet fiducials leave the frame.
prints on its own page, at true size, on A4 or Letter
- Print it. In the print dialogue turn OFF any scaling or fit-to-page option you can find, then measure the printed ruler with any ruler you own and type what you read below. If the driver ignored you, this step catches it.
- Photograph the sheet square on, in even light, with the slanted edge and at least two marks in the frame.
- Put the measured square on the edge, switch the click mode above to mark a printed reference point, and click the two marks.
mm
not calibrated: the printed ruler has not been measured, so millimetres are refused.
The two marks I clicked are:
no marks clicked yet
The sheet itself is checked the way every printable sheet here is checked:
node scripts/check-print-geometry.mjs renders this page's print path through
a headless browser, rasterises the result and measures where the four fiducials actually
landed, in millimetres, on A4 and on US Letter. Nothing on the sheet reaches the paper
edge, because most printers cannot reach the last four to six millimetres all round.
The edge on that sheet is drawn at 5.000 degrees, mid grey against unprinted paper, which is roughly a four-to-one edge, low enough that nothing in the chain is driven into black or white. It is our own geometry and not a reproduction of any published test chart.
Every free choice, and what each one is worth
A measurement chain like this one has perhaps ten places where a reasonable person would choose differently, and almost none of them are ever reported. Each table below is the same estimator with one thing changed, computed here, now, in front of you: either the choice itself, or the specimen that makes the choice cost something. A choice priced only on a specimen that cannot show its price is not priced at all, which is why two of the tables below are specimens rather than settings.
Does the answer know the angle? It must not
The true answer barely moves with the edge angle, so a correct estimator's answer must not move either. Across the angles the geometry gate admits, this one varies by … percentage points. The … shallow rows below two degrees are run with the gate switched off, to show what is on the other side of it.
| degrees | true MTF50 | measured | residual |
|---|---|---|---|
| computing… | |||
Does it know the contrast? It must not, and then eight bits run out
The response is a ratio normalised to 1.000 at zero frequency, so how dark and how light the two sides are should cancel exactly. The table below is not a handful of chosen pairs: it is the symmetric family swept on a fixed grid, … pairs from 0.06/0.94 down to 0.48/0.52 in steps of 0.02, every one drawn and measured here just now. While the edge still spans at least 70 code values the cancellation holds to within … per cent, and the worst row of the sweep is at …. Below that the 8-bit quantisation of the output takes over and the worst row is out by …, at ….
It is not a threshold and it is not a drift. It is quantisation noise whose envelope widens as the code values run out, so a narrow edge can still land better than a wide one: … rows below 70 code values beat the worst row above it, and the best of them is out by only …. What the sweep bounds is the envelope, not any one row, and a page that quoted its four prettiest rows could have claimed a figure several times better than this one. That is why the sweep is regular, why the number quoted is its worst row rather than its best, and why the verifier re-runs the same family at a quarter of this step and fails if anything finer beats the bound printed here. It is a limit of the measurement, measured rather than asserted, and it is why the printed target is deliberately not a faint grey on grey.
| levels, linear light | code values spanned | measured | residual |
|---|---|---|---|
| computing… | |||
The choice nobody mentions that moves the answer by twenty per cent
After fitting a line through the edge, you have to decide how far either side of it to keep. Burns and Williams call this limiting the data length. With a noiseless specimen the choice does nothing at all, which is exactly why it is easy to leave unstated. Add half a per cent of pixel noise, keep the whole 128-pixel row, and the answer falls by … per cent, because a little noise anywhere in the row drags the centroid that locates the edge. Keep only 16 pixels either side and the same frame gives … per cent. This page keeps 16.
| pixel noise | ±8 px | ±16 px | ±32 px | whole row |
|---|---|---|---|---|
| computing… | ||||
The same choice, priced against the thing that pulls the other way
That table makes the short window look free, and a page that stopped there would have priced its own choice against the one failure the choice happens to fix. Limiting the data length throws the edge profile's tails away, and there is something that lives in the tails: veiling glare. A lens scatters a few per cent of the light from the bright side into a broad, shallow halo, so the true response drops while the profile near the edge barely changes. The generator above draws that from the same expression as the sharpener, with the gain negative instead of positive, and states its true response in the same closed form. Each cell is the residual against that closed form:
| halo | true MTF50 | true cost of the halo | ±8 px | ±16 px | ±32 px | ±64 px |
|---|---|---|---|---|---|---|
| computing… | ||||||
The halo costs the true response …. The window this page ships cannot see it and reports …, while the whole row lands at …. The two tables want the window moved in opposite directions, and neither is wrong: 16 pixels either side is right against noise, by twenty points, and wrong against glare, by four. This page keeps 16 and prints both numbers rather than the one that flatters the choice. A reader whose own frame has a bright window or a lamp in it should read their number as an upper bound.
The one difference from the standard, priced
This page fits the edge with a straight line. The standard's fourth edition moved to a
fifth-order polynomial, which is named further down and, until now, was only named. Here
is what it costs. bow below is a sagitta in pixels at the middle of the
region, falling to zero at its top and bottom rows: a printed sheet that will not lie
perfectly flat, or any lens distortion at all, delivers exactly that. The closed form
does not move with it, and that is the point, because a curved edge is a fact about the
thing photographed and not about the lens. Everything lost below is lost by the
straight-line fit.
| what is wrong with the edge | rms of the row centroids about the fitted line | measured | residual |
|---|---|---|---|
| computing… | |||
The last two rows are not bowed at all. They are straight edges under pixel noise, and they sit in the same table because both failures raise the same diagnostic and every degraded row comes back low. The rms scatter of the row centroids about the fitted line is … pixels on the clean straight edge, … at a pixel of bow and … at one per cent noise. That number is computed for your own frame too, and it is now printed beside your own reading.
It is a direction of doubt and not a conversion factor, and the table is arranged so you can see that rather than take it on trust: half a per cent of noise raises the scatter to … pixels and costs only …, because the limited window above absorbs it, while … pixels of the same scatter arriving from bow costs …. So a high number means the fitted line does not describe your edge, which is a reason to distrust what sits under it. It does not say which of the two you have, and this page will not pretend otherwise.
The corrections in the chain
Three of the steps are corrections for the measurement's own instruments rather than for anything about a camera, and each row below is the same specimen run with that one correction taken out, so you can read what it was worth. The fully corrected pipeline sits … per cent from the closed form on the anchor. That residual is this implementation's bias.
| pipeline | MTF50 | residual against the closed form | what the step was worth |
|---|---|---|---|
| computing… | |||
And it is not inside the error bar
The interval this page prints, beside your reading and beside every specimen, is 96 resamples of that frame's own rows in blocks of eight, recomputing the whole estimate each time, combined in quadrature with the frequency-axis term the fitted slope's standard error contributes. A bias is by construction identical in every resample, so no bootstrap can see it. The interval is repeatability. It is not accuracy, and adding the two would be inventing a number rather than measuring one.
The size of that gap is not an argument either. Here are the four specimens whose true answers are known exactly, each against the interval this page would print for it:
| specimen | closed form | measured | offset | 95% interval | offset ÷ interval | closed form |
|---|---|---|---|---|---|---|
| computing… | ||||||
On the anchor the offset is … cycles per pixel against an interval of ±…, which is … times wider than the bar printed next to it. On … specimens the exactly known closed form falls outside this page's own 95 per cent interval. That is what an interval of repeatability means, and it is why the slate above is scored against two per cent of each true value rather than against the interval: an instrument graded on its own error bar would grade itself on how steady it is, not on whether it is right.
The linearisation is the one with teeth. Pixel values are not proportional to light: sRGB bends them, and the slanted-edge method needs light. Skipping that step moves the answer by … percentage points on the four-to-one edge above and by … on a near black-to-white one, which is the second reason the printed target is low contrast. Your camera's true transfer curve is unknowable from a web page, so sRGB is assumed. That assumption is the largest thing on this page that cannot be checked from inside it.
What it refuses, and why refusing is the harder half
Every edge finder ever written will hand back the centroid of noise if you let it. These six specimens are run through the identical estimator, and for each of them the correct output is not a number:
| specimen | required | returned | what it said |
|---|---|---|---|
| computing… | |||
…
The upright edge is the interesting one. An edge parallel to the pixel columns is useless to this method because every row crosses it at the same place, so the quarter- pixel bins that the whole measurement is built on never get filled. The page refuses it on an angle rule, at two degrees. Switch that rule off and point the estimator at a perfectly upright edge anyway, and …
The … shallow rows in the angle table above show the other side of this: down to … degrees the method still answers a clean specimen, and answers it to within …, so the two degree floor is a margin rather than a cliff, and this page says so rather than implying a precipice that is not there. A clean synthetic specimen is not a photograph, though: what fails first below two degrees on a real frame is the bin filling, and the upright row above is what that failure looks like when it is complete.
What this number is not
- It is not your lens. It is the response of print or scene, plus
illumination, plus lens, plus sensor, plus demosaic, plus noise reduction, plus tone
curve, plus sharpening, plus whatever encoder the frame came through, referred to the
plane of the thing you photographed. ISO 12233 makes the same point about the word MTF
itself, in the note attached to its definition: because digital cameras are sampled
systems with spatial colour sampling and typically nonlinear processing,
a meaningful MTF of the camera can only be approximated through the SFR
(3.18, Note 1 to entry). That is why the word on this page is response, and why MTF is used only for the synthetic specimens, where the system really is linear. - It is not comparable with anyone else's. Different distance, different light, different framing, different subject. Two readers measuring the same phone will not get the same number, and a number from this page does not rank one camera against another.
- It is not line pairs per millimetre at the sensor. That needs the pixel pitch, which no browser exposes and which manufacturers state inconsistently. Cycles per pixel and cycles per millimetre on the target are both honestly measurable here. Sensor-plane lp/mm is not, so this page refuses it by name rather than inventing a plausible one.
- It is probably focus or hand shake, not optics. A hand-held phone close to a sheet of paper is far more likely to be limited by focus or motion than by its lens. That is the honest answer to what your camera actually resolves right now, which is a different question from what it could resolve.
- It is only defined for this frame. Modern pipelines sharpen adaptively, by content and by region, and fuse several exposures. The response is not even the same across the picture, so there is no such thing as the response of your camera, only the response it gave to this square of this frame.
The check, in full
…
…
Named choices, all of them: region of interest 128 × 128 pixels, placed by you; limiting data length ±16 pixels; oversampling 4 bins per pixel with empty bins linearly interpolated and counted; window Tukey, α = 0.5; transform zero-padded to 512; luminance 0.2126 R + 0.7152 G + 0.0722 B after the sRGB decode; edge fitted by ordinary least squares on the row centroids, a straight line rather than the fifth-order polynomial the current standard specifies, which costs what the bow table above measures; corrections for the discrete derivative, for the quarter-pixel bin and for the edge angle, each taken out in turn in the table above; peak searched at or below 0.5 cycles per pixel only, since above the sampling limit the ordinate is aliasing; refusal thresholds of 2 to 15 degrees, 0.1% clipped pixels, 10% empty bins and ±15% interval width; interval from 96 bootstrap resamples over blocks of 8 image rows, recomputing the entire estimate each time, combined in quadrature with the frequency-axis term the fitted slope's own standard error contributes.
To recheck all of it outside this page:
node research/slanted-edge-mtf/verify-slanted-edge-mtf.mjs, which loads
these same three files and recomputes every number above from them, and
node scripts/check-live-sensor.mjs --only=slanted-edge-mtf, which drives
the button above in a real browser with a camera stream whose true answer this page has
never seen.
Sources
- P. D. Burns and D. Williams, Refined Slanted-Edge Measurement for Practical
Camera and Scanner Testing, Proc. IS&T 2002 PICS Conference, pp. 191-195. The
chain used here is that paper's Figure 1, and the limiting-data-length step is from it.
The frequency-axis uncertainty is not: that paper's slope-error result is an ordinate
effect, an effective attenuation sin(πmsΔu)/(πmsΔu) from misalignment
over the projected slit, in which Δ is that paper's
original data sampling interval
, s the slope misalignment error, u the spatial frequency and m the length in pixels of the slit that projection along the edge is equivalent to synthesising. Δ is a length, not a differential. The term this page adds is instead its own propagation of the fitted slope's standard error into the frequency axis, which is where the term that matters near MTF50 lives. This page implements the method as published in the open literature, not the standard's own algorithm, and the difference is named above. PDF - ISO 12233:2024, fifth edition, dated 2024-09, titled “Digital cameras — Resolution and spatial frequency responses”. Clause 6 is the edge-based spatial frequency response, 6.1 General at page 10 and 6.2 Methodology at page 13; Annex D, the e-SFR algorithm, is normative and starts at page 32. Its foreword records that the subtitle of Annex D was corrected in this edition to state that the annex is normative, having been listed as informative in the fourth edition. The definitions quoted above are 3.18 and 3.20. The one thing this page says about the standard's own algorithm, that it fits the edge with a fifth-order polynomial where this page fits a straight line, is not read from Annex D, which is behind the paywall and which we have not read. It is read from the foreword of the fourth edition, ISO 12233:2023, whose list of changes records that the e-SFR algorithm was modified by using a Tukey window, by using a fifth-order polynomial equation to fit the edge, and by correcting for the edge-angle sampling. That foreword is in the free preview of that edition. All of the above is visible in the publishers' free previews; the standard itself is copyright and, beyond the two short attributed quotations above, is not reproduced here, and neither is its test chart. catalogue entry
- G. D. Boreman, Modulation Transfer Function in Optical and Electro-Optical Systems, SPIE Press 2001, Tutorial Text vol. TT52, ISBN 9780819441430, DOI 10.1117/3.419857, for the detector-footprint transfer whose first zero sits at one over the detector width. The link goes to that volume; the second edition of the same book is a different volume of the series, TT121 (2021), and is not what is cited here. SPIE
The Wasteland's printable pages write to paper and then go dark. This one asks for the paper back.