The Artificial Wasteland · a sensor pointed at your own room

The Lamp That Is Not a Point

Twice as far, four times less. Everyone half-knows it, and at arm's length from a lamp it is measurably false, by an amount you can predict exactly. This page makes your camera into an instrument good enough to catch it, by refusing to trust a single pixel value until it has worked out what your camera's pixels mean.

The inverse square law is a statement about a point. Real light comes off a surface with a size, and near that surface the fall is slower: a disc of radius a gives, exactly, a local exponent of p(r) = 2/(1 + (a/r)²), which is 1.000 when you are one radius away and only reaches 2 in the limit. Below is the sweep, already measured, from a lamp we generated so we know its answer to the last digit. Drag it.

90 mm

measuring…

Two panels, one lamp. The left one is the card being lit, and it collapses. The right one is the lamp's own lit surface, and it does not move at all. That is the whole of the confusion the law is usually taught into: brightness of a surface does not fall with distance (which is why no telescope can make the Moon's surface brighter than your naked eye already sees it), illumination by that surface does. We will use the first fact as the ruler that measures the second.

The exponent, and the exponent your camera would have handed you

The frames behind that slider are shipped with this page: small pictures of a synthetic room, generated from a closed form so their answer is known before anything runs. The same estimator that will look at your camera looked at them, first, in your browser, just now.

recovered from the frames

±

the fitted exponent p, with a 95% bootstrap interval over lamp positions. Dimensionless.

true value, closed form

the least squares slope of ln E against ln r for E = πLa²/(a²+r²) over these ten distances. No measurement involved.

if we had believed the pixels

the identical reduction with the response curve replaced by the identity: code value taken for light. Wrong by , which is % of the true answer.

That third number is the reason this page exists. A camera does not hand you light. It hands you a code value that some undisclosed curve produced from light, and on the web platform there is no way around it: there is no raw sensor path in any browser, video drawn to a canvas is display referred, and a still capture is a JPEG. Assume the pixels are proportional to irradiance and fit an exponent and you have fitted your own gamma. On the anchor above, that assumption comes back with two thirds of the answer and no sign that anything went wrong.

On your camera that gap could come out anywhere, including zero. Every real transfer curve has a nearly straight stretch somewhere, and a sweep that happens to land inside one needs no correction at all. When this page looks at your room it prints both numbers and says which case it found, because the correction turned out to be zero here and no correction was needed are the same sentence only if you actually measured.

So the curve gets measured, not assumed

The trick is to make the lamp emit an exactly known relative amount of light without knowing anything about how any screen encodes a number as brightness. Drive k of 16 sub-areas white and the rest black, in a dither too fine for the camera to resolve at that distance, and the surface emits k/16 of the flux it emits at full coverage. Whatever that panel's gamma is, and wherever its brightness control is set. Coverage is area, and area does not care how a code value maps to light. What it does not survive is a panel that changes its white level as more of it lights up, which is a thing real panels do on purpose; that one is measured and sized below, with the three other faults these guards cannot see.

It survives a leaking black, too. If the dark sub-areas still emit a fraction b of a white one, the card receives A + (b + (1-b)k/16)·D, and the leak cancels in the ratio (E_k − E_0)/(E_16 − E_0) = k/16, exactly. It cancels whatever colour the leak is, too, which is worth saying because a leaking black is usually not the same colour as the white beside it: a lit sub-area contributes one fixed amount to whatever your sensor happens to be sensitive to and an unlit one contributes another, so the ratio counts the difference between them and neither spectrum enters it. That is the one photometric fact this whole page leans on, and it is inherited with credit from the halftone ladder that measures a display's own curve with your eye. This page is that instrument pointed the other way: that one measures number to light with an eye, this one measures light to number with a camera.

the response curve, recovered four times over

curves, one per shipped specimen, each fitted from that specimen's own coverage ladders and from nothing else. A response curve is only ever pinned down up to a scale and an offset, because every constraint it is fitted through is a difference, so before they can be compared at all they are tied to the same two code values, here , which is the range all four specimens reached. After that nothing is free, and across it they disagree by at most % of full scale. Four independent recoveries of one camera. The dashed diagonal is the claim that a code value is proportional to light, which is where the red number above came from. Residual left over by the fitted curve on the anchor: % of full scale, over code values . Above 2% of full scale the page refuses to print an exponent at all. What that guard does and does not catch is the next section, and it is measured there rather than promised here.

What the ladder catches, and what walks past it

A curve fitted to a ladder is worth something only if it can fail, so below it is being made to. Every row is a camera we built. It takes the anchor's own frames and returns the frames a differently behaved pipeline would have written from the same light, and the estimator is then run on those, unchanged and never told that anything happened. All three curve faults are applied at the same strength, , so no row can have been tuned to the answer it gives.

constructed cameras, not measured ones. Every number in this table was computed in your browser in the last second, from the shipped anchor.
the camera we builtrecovered pwhat the page did about it
running…

Of the four faults, was refused and got past the guards with an answer outside the tolerance the slate uses. That is the honest result and it is why this panel is here rather than a sentence promising the ladders would notice. The one that is caught is caught by monotonicity and not by the residual: across every fault above, the worst ladder residual was % of full scale, against a refusal threshold of 2%.

We could not build a camera that reaches that threshold at all. Every ladder disagreement we could construct that was large enough to get there first drove the fitted curve back on itself, and the monotonicity refusal fired instead. So the residual is a consistency figure that gets printed and a backstop that has never been seen to fire, and this page will not call it a tone-mapping detector. What it does say, and what the rows above show, is narrower and true: one curve has to fit every ladder at once, and a camera that keeps its numbers rising with the light can still be wrong without anything here going red.

The second dismissal: is it not just 2, by definition?

No, and this is the part worth your attention. Geometry gives 2 for a point. For a uniform Lambertian disc of radius a, the on-axis irradiance is exactly E = πLa²/(a²+r²), and differentiating gives the local exponent p(r) = 2/(1 + (a/r)²) with no free parameter once you know a. Set the emitter's radius below and the prediction moves; the dots do not, because they are already measured.

a = 30 mm. Root mean square gap between the measured local exponents and the closed form at that radius: . The five times rule says a source is a point to within 1% at ten times its radius, here , where the closed form gives p = . And the honest limit of the plot, marked on it: past the predicted departure from 2 is smaller than the scatter of the dots themselves, so out there this measurement cannot tell the prediction from a flat 2 and neither can you. The claim lives at the near end. That line moves when you move the radius, which is the same fact from the other side: a small emitter has nothing to show over these distances.

Drag the radius away from 30 and the curve leaves the dots. That is the check that this is a measurement and not a restatement: a wrong a makes a visibly wrong prediction, and the reader can produce one on purpose.

The five times rule, which lighting handbooks state as folklore, falls straight out. Ryer's Light Measurement Handbook says the distance to a light source should be greater than five times the largest dimension of the source, and that at ten times the source radius, which is five times the diameter, the error from using the inverse square is exactly 1 %. For the disc that is not a rule of thumb but an identity: the point source formula overstates the irradiance by (a²+r²)/r², which at r = 10a is 1.0100, exactly one per cent, and the local exponent there is 2/1.01 = 1.980198. Both are recomputed by this page's verifier from the algebra, not quoted.

The slate: what earns the right to report your room

Four specimens over three known answers, one estimator with no branch in it that could tell which is which. The fourth is the anchor re-shot with the camera's exposure hunting, so it carries the anchor's answer and a different way of hiding it. Two of them are controls whose true exponents are 1 and 0, which a constant cannot reach from 1.95.

every measured number in this table was computed in your browser in the last second. The true column is closed form, and this page's verifier recomputes it from the algebra.
specimenroletrue pmeasured passume linear
uniform disc, a = 30 mmanchor1.951698
the same disc, exposure huntinganchor1.951698 ±
infinite uniform linecontrol1.000000 ±
infinite uniform planecontrol0.000000 ±

The second row is the one that would kill a lazy calibration. That camera's exposure hunts by % across the sweep, and leaving it undivided drags the fitted exponent from down to : the same frames, the same recovered response curve, one step of the reduction left out. What divides it back out is the lamp's own lit cells: they are a luminance, and a luminance does not fall with distance, so their recovered linear value at each position is the camera's gain at that moment and nothing else. Skip that one division and this row goes red, which is the whole reason a second anchor is here.

The third specimen is the one that stops this being a private language. An infinite line source falls as 1/r, exactly, and a plane does not fall at all: the same geometries Stari and colleagues measured in a real room with a phone's light sensor. Nothing in the estimator knows which specimen it is holding, so if it returned a constant, or the anchor's table, or anything that was not a measurement, those two rows would go red and the instrument would refuse to look at your camera at all.

the slate, as the kit reports it

running…

and the same slate handed to a deliberately hard-coded estimator

running…

That second block is the control on the control. It replaces the estimator with one that always returns the anchor's published value and re-runs the identical slate. It sails through the anchor and dies on the controls, which is the demonstration that passing the anchor is not enough.

What the ladder cannot see, because it is not the camera

Every row of that earlier panel was a camera, and that is the limit of what a lookup table can build: a camera turns light into a number, so you can apply one to a picture that already exists. The four things below are not the camera. They are the lamp, the card and the room, and each of them changes the light, so the only honest way to size them is to render the whole sweep again with the fault in the scene and hand it to the same estimator without telling it anything moved. That is done in research/the-lamp-that-is-not-a-point/systematics.mjs, offline, because this page ships pictures rather than a renderer. Every number in the table was measured that way, and this page's verifier fails if any of them stops being what it measures.

the same ten distances, the same estimator, one thing changed about the world in front of it. True exponent 1.951698 throughout.
what changedrecovered pladder residual, % of full scalewhat the page did about it
nothing: the anchor as shipped1.9416 0.015the reference every row below is read against
the lamp's white level falls 2% as the emitter fills1.9722 0.022nothing, and the answer moves by +0.031
the same, at 5%2.0204 0.040nothing, and it now reads above 2
the emitter's radiance falls as cos θ instead of being Lambertian 1.92960.015 nothing, and the answer moves by −0.012
the patch of card inside the yellow box is 102 mm across1.8559 0.012nothing, and the answer moves by −0.086
a sheet of paper fills the box: 210 mm across1.6259 0.031nothing, and the answer moves by −0.316
the lamp's own light off the walls of an ordinary room1.9264 0.016nothing, and the answer moves by −0.015
the same, in a small bright bathroom1.9008 0.015nothing, and the answer moves by −0.041

Not one of them refuses. The worst ladder residual in that column is 0.040% of full scale against a threshold of 2%, every fitted response curve stays monotone, and every row hands back a confident number. And three of them are larger than the whole departure from 2 that this page exists to show, which is 0.0483. So here is each one, and what a reader can do about it.

The lamp that dims as it lights up. The coverage argument is a statement about area and it is exactly true about area. It is not true of a panel whose driver lowers the white level as the lit fraction grows, which is what automatic brightness limiting does on an OLED and what zone dimming does on a backlit LCD. TFTCentral measured one OLED panel at about 4.7 times as bright over a small white window as at full-screen white, so this is not a rounding error on real hardware. Nothing on this page can catch it, and the reason is exact: the droop is a function of k and of nothing else, so all three coverage ladders agree with each other perfectly. They are simply all wrong in the same way, and agreement is the only thing the residual measures. What would catch it is a second run: set the lamp's disc radius to 30 mm, then to 60 mm, and compare the two exponents. Four times the lit area is, to first order, four times the droop, so a panel that is doing this gives two different answers and a panel that is not gives one answer twice.

The card patch has a size. This is the one that bites hardest and it is the easiest to fix. The closed form on this page is the irradiance on axis. A point on the card that sits sideways from the axis by ρ receives r²/(r²+ρ²)² instead, so the average over the yellow box carries a factor that changes with r, and the exponent comes back low by about (W² + T²)/(3r²) for a box covering W by T of card. This is not the vignetting hazard: that one is the camera's and cancels because the box never moves in the frame. This one is the geometry between the lamp and the card, and it does not cancel, because it is a different geometry at every distance. Over this sweep, whose nearest position is 90 mm, an A4 sheet filling the box costs a third of an exponent. A patch 25 mm across costs 0.006. So the instruction below is to get close enough that the box covers a small patch of card, and the page says out loud that it cannot see how big yours is.

The emitter is only roughly Lambertian. p(r) = 2/(1+(a/r)²) is exact for a disc whose radiance is the same in every direction, and a screen is close to that but not equal to it. For an emitter whose radiance falls as cos θ the exponent at one radius is 0.8204 rather than 1.0000, at five radii 1.9042 rather than 1.9231, and the fit over this sweep lands 0.012 low. That is the real content of "no free parameter": there is no free parameter given that the emitter is Lambertian, and this page assumes that rather than measuring it. Worth knowing, and worth not hiding: the two controls are immune to it. An infinite uniform line gives p = 1 and an infinite plane gives p = 0 for any angular law, so those two rows of the slate test the instrument and not the model, which is exactly what a control is for.

Your walls are in the answer, and here is by how much. The lit minus unlit subtraction removes your room's own light exactly. It does not remove the lamp's light coming back off your walls, because that arrives only when the lamp is lit. By the lumen method that indirect term is πρ(a²+r²)/(A(1−ρ)) times the direct one, which grows as : it flattens the fall exactly where the page is trying to watch it steepen. In an ordinary 3 by 3 by 2.5 metre room with walls at 0.6 that is 3.5% of the direct light at 600 mm and costs 0.015 of exponent; in a small bright bathroom it costs 0.041; in a large dark room, 0.002. This is the one systematic here you fix by choosing where to stand.

Now your room

You need two things: a camera that can sit still, and a second screen to use as the lamp. The second screen is not a convenience. It is what makes the calibration exact, because it is the thing that can be driven at a coverage we both know.

  1. Open the lamp page on your other device, or in another window on a second monitor, and turn its brightness up to full. Leave it alone after that: changing the brightness mid sweep is the one thing that breaks this. Full brightness is also where a panel that dims by chopping its light in time is chopping least, which keeps a short exposure from catching a random slice of the chop.
  2. Prop a sheet of white paper up as the card, and get the camera close enough that the yellow box covers only a small patch of it, a few centimetres across, with the lamp's striped code band inside the green box. Small is not cosmetic. The box is what gets averaged, and a box that swallows a whole A4 sheet costs a third of an exponent for the reason in the table above.
  3. Put a tape measure between the lamp's screen and the paper. The lamp page will call out ten distances, from 90 mm to 600 mm. Move the lamp, never the camera: the lens's own vignetting cancels only if the paper stays in the same part of the picture.
  4. Press measure here and let it run while the lamp page walks its sequence. The code band tells the camera which step it is looking at, so the two devices need no clock between them.

the instrument

not looking

The slate above runs whether or not you ever grant the camera. Nothing below happens until you press a button.

What this refuses to do

The origin you cannot see

One more result, and it is the trap this experiment is famous for. Distance from what? You measure from the glass, but the light behaves as if it came from somewhere behind it. Let the origin float, refit, and the anchor's exponent becomes at an offset of mm.

Two, near enough. The number everyone expects, obtained by adding a free parameter that absorbs the very departure this page is about. And here is the part that makes it a trap rather than a choice: the floating origin fits better. The root mean square residual in log space falls from with the origin at the glass to with it free, a factor of . A free offset and a source's own near-field curvature are the same shape in log space, so the offset eats the result and hands back a tidier line while doing it. A reader who fits the better model and reports what comes out has not measured the departure. They have absorbed it, and been rewarded with a smaller residual for their trouble.

Both numbers are printed on purpose. The fitted origin is not a correction here: we know exactly where the synthetic lamp is, and the offset it finds is the finite size of the source wearing a disguise. On your own sweep the offset is real and unknown, which is why the page shows both and calls neither the answer.

The check

Every measured number above was computed in your browser, from the shipped frames, by the same function that would look at your camera. The rest are closed forms: the true exponents, the ten distances, the exact one per cent. This page's verifier recomputes each of those from the algebra rather than reading it back off the page. The kit's own attestation:

running…

every free choice this estimator makes

Every one of them, not the interesting ones: the verifier fails if a single entry of the estimator's own settings block is missing from this table, or if a value here is not the value in the code.

channelgreen onlynarrowest band, best signal to noise, least demosaic cross-talk
region of interest pxfixed at 30 to 70% across and 68 to 94% down the frame, so vignetting and cos⁴ falloff are the same constant at every distance
where the code band is looked fortop 55% of the framebelow that is the card, and a bright card must not be read as a band
a band pixel is lit above45% of that frame's rangebetween the darkest and brightest pixel in the search area, so no absolute level is assumed
smallest readable band24 px wideand no wider than 98% of the frame, which would mean the whole picture was mistaken for the band
a band cell must differ from its partner by20 code valuesotherwise the bit is unreadable and the frame is dropped rather than guessed
response curve17 knots, monotone, piecewise linearends pinned at 0 and 1, over the code values this session actually used
smoothing on that curve1e-3 per constrainta second difference penalty, so a knot with no data near it cannot run away
refuse if ladder residual exceeds2% of full scalea backstop; nothing we built reaches it before the monotonicity check fires
and at any single lamp position3% of full scaleso one bad position cannot be averaged into an acceptable whole
drop a distance below3 code valuesof lit minus unlit signal
minimum lamp positions for a fit6of the positions in the step protocol; all survived on the anchor
minimum coverage constraints12rungs strictly between dark and full, across all positions, before a curve may be fitted at all
reject a frame above0.1% clippedregion of interest pixels at 0 or 255
interval2000 resamples, 95%bootstrap over lamp positions, so a mis-placed lamp is inside the interval
and its seedfixedso the interval printed here is the same one you would get on a reload; it is not resampled fresh each visit
frames kept per step4on the live path, averaged; the shipped specimens carry one frame per step
capture rate asked for10 per secondwhat the camera actually delivers is printed under the viewfinder
frame sampled down to320 px widethe region of interest is a fraction of the frame, so this sets how many pixels it averages
and tall240 pxthe shipped specimens are 160 by 120, which is why their region of interest is smaller

One thing that is not a free choice: the coverage ladder itself. The lamp shows k/16 for k = 0, 2, …, 16 at three of the ten positions and 0, 8, 16 at the other seven, and that is the protocol both devices walk, not a setting inside the estimator.

where the interval comes from, and where it does not

The anchor's interval runs from to . Separately, the 8-bit floor propagated analytically through the recovered curve is if your sensor's own noise dithers the rounding across the region of interest, and if the patch is dead flat and it does not dither at all. Both are printed because which one you get depends on your camera and the page cannot know. Neither includes where you actually put the lamp: a 5 mm placement error at the nearest position, 90 mm, is more than 5% in r all by itself.

what would make this wrong

The offline verifier at research/the-lamp-that-is-not-a-point/verify-the-lamp-that-is-not-a-point.mjs regenerates every shipped specimen from the closed forms and compares hashes, re-derives the exponents by two independent routes, re-implements this estimator from scratch in Node and requires the two to agree, re-renders the whole sweep with each of the four faults above in the scene to size what nothing here can catch, and drives this page in a real browser to check that what you are reading is what the estimator produced. It runs 255 checks and prints 255/255 checks green, and that sentence is not left to drift: the last thing the verifier does is read this number back out of this page and compare it with the number of checks it just ran, so the file goes red if the two ever part company. The live path itself is checked by scripts/check-live-sensor.mjs, which hands Chromium a video of a known sweep as the camera and asserts this page reports the right exponent, then hands it a different known sweep and asserts the answer moves.