Brownian motion · A later measurement model

The particle. The camera. The estimate.

How do camera noise, exposure blur and drift change what displacement data can tell you? A camera averages motion during exposure and adds uncertainty to every position. Discover why those errors change what you can infer, even when the particle follows exactly the same path.

Observe, then infer

What did the camera change?

Predict before the numbers

Add camera noise. Does the naive diffusion estimate go up, down, or stay the same? Does the correlation between neighboring steps become positive, negative, or zero?

Three relations the model could have

The result appears when you choose, say you have one in mind, or skip.

1 · Re-observe the same path
With the same settings, the same seed replays the same camera noise. Any whole number from 0 to 18,446,744,073,709,551,615 works.

Exposure starts at each frame time and cannot exceed the spacing. Noise, stage drift, coordinates and frame selection leave the retained physical path unchanged.

2 · Declare what is known about noise
With the same settings, the same seed replays the same stationary clicks. Any whole number from 0 to 18,446,744,073,709,551,615 works.

Changing the statistical procedure alone reuses the same observations. Stationary clicks have independent errors and an unknown constant feature location; they are not clicks on the moving particle.

3 · Change the physical setup
These changes start a new physical run
With the same settings, the same seed replays the same physical path. Any whole number from 0 to 18,446,744,073,709,551,615 works.

Fluid drift belongs to the physical path. Stage drift belongs to the measurement. Either produces apparent drift, but only the first changes this recording.

Comparisons use accepted settings, not unfinished drafts. Reset stage drift with a camera-error comparison; fluid drift remains an explicit physical input.

−8.74250−1.3687506.0052100x position (μm)Exposure start time (s)
Dashed: retained position at frame start. Dotted: exposure average before stage drift or localization error. Solid: camera observation. Lines only connect the selected frames; the y coordinate, when selected, is included in all estimates and tables.

Four estimates, different assumptions

Accepted diffusivity estimates (μm²/s)
Generating value0.42944
Ignore camera error and drift0.40608
Fit drift, but ignore camera error0.4067
Covariance estimate; known synthetic drift removed0.51042
Disjoint pairs; noise and exposure corrected0.47601

Static worked example

Model note
  • D, ignoring camera error and drift (naiveD), D, fitting drift but ignoring camera error (centeredD), D from the covariance, with the known synthetic drift removed (covarianceD), D from disjoint pairs, with noise and exposure corrected (pairD), Covariance-based noise estimate (covarianceNoiseVariance), Stationary-click variance (stationaryNoiseVariance), Increment variance, camera model (expectedVariance), Adjacent-increment covariance, camera model (expectedCovariance), Increment variance, sample (sampleVariance), Adjacent-increment covariance, sample (sampleCovariance), Asymptotic sampling SD of the increment variance (sdVariance), Asymptotic sampling SD of the adjacent-increment covariance (sdCovariance), Expected value of the estimate that ignores the camera (naiveExpectation), Generating value of D (modelDiffusion), Residual degrees of freedom of the disjoint pairs (pairDegrees), Number of disjoint pairs (pairCount), Whether the pair confidence set is empty (emptyPairInterval), Whether the pair set was cut at zero diffusivity (pairLowerClipped), Random draws in the recording (recordingDraws), Random draws executed for this request (requestDraws), Camera and click stream evaluations (measurementDraws), Whether the existing recording was reused (reusedRecording), Whether the existing observation was reused (reusedObservation), Memory held by the kept recording, in bytes (retainedBytes), Fitted drift (driftFit), Bounds on the noise variance (noiseInterval), Interval for D, ignoring the camera and drift (naiveInterval), Interval for D, fitting drift only (centeredInterval), Interval for D from disjoint pairs, with a camera model (pairInterval), Camera positions in each frame (positions), Latent positions at the start of each exposure (idealPositions), Positions averaged over each exposure (blurredPositions), Displacements between camera frames (increments), Exposure start times (times), Independent stationary-feature positions (stationaryClicks), The first kept latent positions, a replay check (latentWitness), Frame spacings in the zero-exposure comparison (speedTimes), Ideal apparent speed at each spacing (idealSpeeds), Apparent speed with noise at each spacing (cameraSpeeds), Ratio of noisy to ideal apparent speed at each spacing (speedRatios), Noise-only crossover interval (speedCrossover), Random draws for the hypothetical experiments (coverageDraws), Intervals from the hypothetical camera experiments, relative to the generating value (coverageIntervals), Hypothetical intervals that ignore the camera and cover the generating value (coverageNaiveCount), Hypothetical noise-aware pair intervals that cover the generating value (coveragePairCount) and Hypothetical experiments with an empty pair set (coverageEmptyCount): Host calculation, by the site's own reference code. Computed in inference.bm08.
  • Seed 1905: with the same settings, the same seed replays the same trial.
  • Computed from the settings last applied (settings revision 1, result 1).
  • Not modeled: Higher-order optical aberrations; only uniform exposure blur and Gaussian localization error.

Static worked example. No calculation has started in this browser.

Accepted physical seed 1905; D = 0.42944 μm²/s; 101 frames in 2 coordinates; spacing 1 s; exposure 0.5 s; localization error 0.2 μm. Stage drift 0 μm/s; fluid drift 0 μm/s.

The covariance estimate uses the explicitly known synthetic drift, not a secretly fitted mean. It has no general confidence interval here. The pair procedure fits drift within its disjoint pairs and does not use generating D. Negative unconstrained estimates remain visible as diagnostics.

Which intervals are actually valid?

Ignoring the camera and drift

The zero-drift independent-increment interval does not apply to noisy, exposed, or drifting observations. Its point estimate remains visible as a deliberately naive comparison.

Fitting drift only

Fitting drift does not remove camera noise or the adjacent-increment correlation. The simple centered chi-square interval is not admitted.

Disjoint pairs, with a camera model

0.32569 – 0.69675

95% target coverage; μm²/s. Conservative procedure combining stationary-click noise uncertainty with disjoint-pair variance uncertainty.

50 disjoint pairs; 98 residual degrees of freedom.

A noisy image is not a new physical trajectory. Keep one wandering particle and change how it is observed. Then compare the estimates that ignore camera error with a procedure that accounts for it.

Look for the camera’s fingerprint

Neighboring displacements share a localization error with opposite signs. Exposure averaging creates a different correlation. A variance alone cannot distinguish these effects.

Per-coordinate moments after subtracting known synthetic drift; μm²
MomentSampleCamera modelAsymptotic sampling SD
Increment variance0.812150.795730.079699
Adjacent-increment covariance0.104340.0315730.0564

The last column is a large-sample MA(1) approximation for the moment estimates, not a confidence interval for D. It is particularly unreliable for very small samples. Expected naive D under this camera: 0.39787 μm²/s.

Inspect stationary clicks and inferred noise

Stationary-click variance: 0.027327 μm². Covariance-based noise estimate: −0.019274 μm². Noise bounds used by the pair procedure: 0.018697 – 0.04333 μm². In exact-noise mode the two bounds are the declared value; otherwise they consume half the allowed error probability.

Independent stationary-feature positions (μm)
Clickxy
10.11424−0.12339
20.0130490.14156
30.301720.061022
4−0.22497−0.18133
5−0.187340.17894
60.23688−0.045071
7−0.035658−0.11956
8−0.0117320.010424
90.0249870.13217
10−0.12084−0.11937
110.134120.16166
120.33155−0.064944
13−0.234340.17682
14−0.043285−0.14566
150.14079−0.051643
16−0.044961−0.049523
170.038157−0.042038
180.22326−0.2329
190.2486−0.27179
20−0.074478−0.41927
210.342350.39974
22−0.138910.069009
23−0.21279−0.089314
24−0.021301−0.1282
250.011022−0.07899
26−0.18005−0.23813
270.0488980.060165
280.12458−0.012509
29−0.10319−0.052014
30−0.053618−0.01365
Read all accepted frame positions
Every selected coordinate; exposure-start time in seconds, positions in μm
StartCoordinateLatent startExposure averageCamera position
0x00.0595930.40616
0y00.493360.58492
1x0.767550.483810.45831
1y1.46641.65311.7576
2x1.35221.93741.7977
2y1.82082.36112.3077
3x1.65821.761.5403
3y4.76495.34065.6798
4x1.82592.22542.4785
4y6.1066.40786.9054
5x2.69182.45242.6495
5y6.62187.10457.14
6x2.30152.28692.2653
6y6.86747.76947.9798
7x2.53763.32023.6063
7y7.8158.12898.3653
8x3.92194.02694.0995
8y7.97728.03567.8886
9x3.6624.03963.9277
9y8.73928.34128.4006
10x4.87845.0355.1915
10y9.85199.60819.6218
11x3.21882.95863.114
11y10.0969.80119.6936
12x1.71131.63581.6848
12y8.69358.65158.8669
13x1.57581.40061.2002
13y9.3879.24049.443
14x2.09852.21492.3735
14y10.44110.0879.9655
15x2.37942.17282.162
15y9.68019.696310.071
16x3.05333.55693.6126
16y10.37710.18510.139
17x2.98363.03053.2455
17y10.01910.52910.491
18x3.95923.88424.1016
18y10.07710.24810.194
19x4.64134.76654.8111
19y10.52810.52510.182
20x6.00525.80675.9848
20y9.66989.685210.06
21x5.67374.58764.7685
21y9.53299.95849.6914
22x3.55483.49273.5383
22y9.29179.71429.7763
23x2.65722.35272.4738
23y9.860610.16910.253
24x2.90662.65672.7187
24y11.14511.57911.513
25x3.02053.25333.3214
25y10.70811.23711.085
26x2.29342.52472.6839
26y10.44810.9710.838
27x1.68842.04061.7957
27y11.69711.54411.487
28x3.36043.34693.3858
28y10.95910.6310.509
29x4.02014.27094.3107
29y10.219.43718.9681
30x3.69983.67063.7236
30y8.87458.85839.2388
31x4.90564.55994.7906
31y9.47589.34299.443
32x5.13825.28295.1973
32y10.679.88810.094
33x4.98664.43354.2462
33y7.69477.04347.0207
34x3.18142.64882.6366
34y4.99344.82964.8252
35x2.45132.11141.8492
35y5.65035.90315.8574
36x3.5834.13994.2714
36y5.40766.47226.5851
37x5.08965.75855.5093
37y7.29426.93736.9627
38x4.98754.48224.757
38y7.53747.57977.5261
39x4.78114.68714.5648
39y6.85217.68467.4462
40x4.19583.77553.5298
40y7.7957.91497.4695
41x4.07654.26184.2868
41y9.14558.59258.4906
42x3.00933.37363.2438
42y7.59177.82927.873
43x2.88043.13743.4481
43y7.97718.52598.2192
44x4.47564.52384.6473
44y8.34958.22938.186
45x5.24295.62745.5681
45y7.99997.93688.2303
46x5.6335.36475.3908
46y8.56889.56119.5009
47x5.03695.06624.7665
47y10.8211.46611.533
48x4.03584.39324.2725
48y11.85111.63211.768
49x5.19795.12335.4984
49y10.33110.25710.197
50x4.72594.72834.9794
50y10.25610.58710.651
51x5.07254.90324.7221
51y9.77719.12119.1527
52x4.24564.17784.245
52y9.50519.54829.5819
53x4.93014.81175.0577
53y9.43639.30269.504
54x5.34375.17715.2083
54y8.84948.83358.5755
55x5.08665.3465.7544
55y8.6448.07187.9578
56x4.98064.35194.1889
56y7.66197.88747.9279
57x4.44483.98833.8757
57y8.55518.9329.1778
58x2.65132.77952.5306
58y8.87019.28689.0655
59x2.58533.00013.0493
59y10.10510.0939.995
60x2.95573.48653.4012
60y10.32110.22810.189
61x3.40513.66523.7788
61y9.966710.25610.348
62x2.74832.6782.9185
62y10.96411.26111.034
63x1.99011.46991.5596
63y11.81512.08512.099
64x−0.51039−1.0691−0.79414
64y11.62611.59611.893
65x−0.55716−0.60743−0.55769
65y11.44710.74110.722
66x−0.63519−0.76667−0.67494
66y10.0039.63759.8175
67x−0.013952−0.28854−0.098885
67y8.69988.7089.1709
68x0.254950.098652−0.1038
68y9.44749.941410.21
69x0.53966−0.0610820.10451
69y10.55611.38511.368
70x−0.9578−1.1715−0.94174
70y12.0912.18512.175
71x−2.5558−2.2148−1.9856
71y12.14211.92811.685
72x−1.1594−1.9262−1.9588
72y11.92711.45511.781
73x−2.6058−3.5836−3.1923
73y11.17111.54711.544
74x−3.8485−3.1959−3.0037
74y12.14511.90812.213
75x−4.0183−4.5268−4.7412
75y12.12311.80111.56
76x−4.5134−4.4151−4.6273
76y12.06612.04612.437
77x−5.3005−5.1999−5.1619
77y12.37312.73612.828
78x−5.7773−6.3041−6.1381
78y13.83114.25314.282
79x−6.6899−6.6807−6.4111
79y1414.11113.931
80x−5.5471−5.4157−5.5802
80y12.99312.57512.341
81x−4.4161−4.292−4.4776
81y12.93912.53812.542
82x−4.9302−5.5939−5.667
82y12.68712.30812.375
83x−5.7146−5.5407−5.5217
83y12.82912.84212.75
84x−5.8769−5.8112−5.6515
84y13.27413.30913.637
85x−6.6215−7.0299−7.1864
85y13.58214.12314.411
86x−7.3474−7.5472−7.5471
86y13.58213.49113.521
87x−7.1866−7.0916−7.1148
87y12.85112.87212.874
88x−6.8293−6.9662−6.9872
88y12.38712.67612.725
89x−7.1942−7.1278−7.1921
89y13.20612.41812.761
90x−7.4735−6.8345−7.1217
90y12.23312.07812.556
91x−7.2876−7.418−7.3593
91y12.50812.15211.962
92x−7.3322−7.543−7.5144
92y11.12811.38611.627
93x−7.9369−7.7487−7.9716
93y10.54210.07310.16
94x−7.2622−7.0985−7.1378
94y11.17911.37711.174
95x−6.4592−6.7737−6.6992
95y10.34510.55410.572
96x−7.2522−7.6636−7.6852
96y9.879910.119.9835
97x−7.7138−7.5058−7.5345
97y9.5329.52149.658
98x−7.2317−7.6763−7.4877
98y8.93118.92598.7872
99x−7.3981−7.4369−7.5648
99y9.46259.09489.1115
100x−8.7425−8.5387−8.3366
100y8.04888.37318.4126

Faster pictures need not reveal a physical speed

This separate analytical comparison holds the accepted diffusivity and localization error fixed, sets exposure to zero, and varies a hypothetical observation interval. It does not create extra frames on the retained quarter-second grid. Neither curve is an instantaneous velocity.

40.250.00250.463387.2886114.65Apparent spread speed (μm/s)Hypothetical spacing (s)
Both axes are logarithmic. Dashed: ideal spread divided by interval. Solid: the same model with localization noise and zero exposure. These are analytical comparisons, not extra observed frames or instantaneous particle velocities.

Noise-only crossover interval: 0.093145 s. At positive exposure, blur also changes the spread; that is not part of this zero-exposure comparison.

Analytical zero-exposure comparison, not additional sampled data
Spacing (s)Ideal (μm/s)With noise (μm/s)Ratio
40.463380.468741.0116
10.926760.968961.0455
0.251.85352.17151.1716
0.06253.7075.851.5781
0.019.267629.7643.2116
0.002518.535114.656.1853

Does the interval procedure cover the generating value?

Compare 100 other hypothetical paths under the same physical and camera settings. The dashed comparison deliberately applies the naive formula even where it is invalid; a pleasing interval is not evidence that its assumptions hold.

Run hypothetical experiments explicitly to compare procedures.

Every trial, including misses and empty sets, is retained. Changing the noise procedure reuses the same hypothetical identities. The primary path and camera observations are not replaced. A realized coverage fraction need not equal the target.

Accepted identity, random streams and limits

Computed by the host reference evaluator inference.bm08.

Retained path and bridge draws: 32896. New latent work for this request: 32896. Camera/click stream evaluations: 524. Hypothetical experiment draws: 0. Retained latent bytes: 131600.

Source identity: source:sha256:37d9f7d44016660356c07919cd2309def99b7f3bf545d811a2b28076a63be54b. Two latent coordinates and exact Brownian subinterval averages are retained on 4,112 quarter-second intervals. Frame errors have separate timestamp-indexed streams. Remeasurement may evaluate the same noise draws again; it does not create a new latent trajectory. No irregular timing, overlapping exposure, censoring, empirical import or arbitrary camera likelihood is admitted. This is host arithmetic, not audited WASM or historical evidence.

A camera does not record exactly where a particle is: it smears the motion over each exposure and adds a small error to every position. Both change the spread you measure, although the particle's own path is the same, and the lab shows which ways of estimating still give the right answer.

The instrument keeps a particle's own wandering, with D = 0.429 μm2/s, apart from what a camera records. Each recorded position carries an error of standard deviation σ = 0.2 μm, and each frame averages the motion over an exposure of 0.5 s in a 1 s interval. A recorded step then has variance 2D(τ − Te/3) + 2σ2 = 0.716 + 0.080 = 0.796 μm2, not 2Dτ, and neighbouring steps are correlated, by DTe/3 − σ2 = 0.032 μm2: the blur links them positively and the shared noisy position links them negatively. Dividing the variance by 2τ, as the 1905 formula would, expects 0.398 μm2/s, 7 percent low, and this sample gives 0.406; its textbook interval is refused, because the steps are no longer independent. Two estimators allow for the camera. One adds the neighbouring covariance to the variance and gives 0.510 here; one uses only disjoint pairs of frames and gives 0.476, with a 95 percent interval from 0.326 to 0.697 μm2/s that contains the true value. Below τ = σ2/D = 0.093 s the camera's jitter outweighs the particle's own motion in any speed computed from positions.

Modern qualifications

None of this is in the 1905 paper, which treats every position as exact. Einstein did warn, in 1906 and again in 1907, that a speed computed as displacement over time grows as the interval shrinks, so it is not the particle's speed. The blur term comes from studies of camera exposure in particle tracking (Savin and Doyle, 2005), the full model of variance and covariance is Berglund's (2010), and the estimator that uses the covariance is due to Vestergaard, Blainey and Flyvbjerg (2014).

Show the code

The site’s own reference code for this law, in TypeScript. It computes the numbers above when the page runs it, and it is what the page uses wherever a FrankenSim result is not available.

cameraMoments · src/physics/reference/inference/observation.ts · this site’s source, as built · sha256:83cfd1186b1aa96f8d0dfdd59a89c84e241a51db1afd7a43d678d18f6070dc1b

This function computes the listed outputs when it runs.

In words

For a shutter open for a time Te out of each frame interval dt, and a localization error of standard deviation sigma, the measured increment variance is 2 D (dt - Te/3) + 2 sigma squared, and neighbouring increments acquire a covariance of D Te/3 - sigma squared. The function returns both, the apparent speed a reader would compute from each, and their ratio. At a diffusivity of 3.1 times 10^-13 m^2/s and half-second frames: an ideal camera gives a variance of 3.1 times 10^-13 and a ratio of 1; a shutter open the whole interval gives 2.067 times 10^-13 and a ratio of 0.816; a localization error of 0.2 micrometres with no blur gives 3.9 times 10^-13 and a ratio of 1.122.

Implementation

export function cameraMoments(p: CameraModel): Computation<
  Readonly<{
    variance: number;
    covariance: number;
    naiveExpectation: number;
    idealApparentSpeed: number;
    measuredApparentSpeed: number;
    apparentSpeedRatio: number;
    crossover: number | null;
  }>
> {
  if (
    !finite(p.D, p.dt, p.exposure, p.sigma, p.drift) ||
    p.D <= 0 ||
    p.dt <= 0 ||
    p.exposure < 0 ||
    p.exposure > p.dt ||
    p.sigma < 0 ||
    ![1, 2].includes(p.d)
  )
    return invalid(
      "Use positive diffusion and spacing, nonnegative localization error, exposure between zero and spacing, and one or two coordinates.",
    );
  const variance = 2 * p.D * (p.dt - p.exposure / 3) + 2 * p.sigma ** 2;
  const covariance = (p.D * p.exposure) / 3 - p.sigma ** 2;
  const naiveExpectation = variance / (2 * p.dt) + (p.drift ** 2 * p.dt) / (2 * p.d);
  // These speeds describe the mean-subtracted coordinate spread, not drift.
  const idealApparentSpeed = Math.sqrt(2 * p.D * p.dt) / p.dt;
  const measuredApparentSpeed =
    p.exposure === 0 && p.sigma === 0 ? idealApparentSpeed : Math.sqrt(variance) / p.dt;
  const apparentSpeedRatio = measuredApparentSpeed / idealApparentSpeed;
  const crossover = p.sigma > 0 && p.exposure === 0 ? p.sigma ** 2 / p.D : null;
  if (
    !finite(
      variance,
      covariance,
      naiveExpectation,
      idealApparentSpeed,
      measuredApparentSpeed,
      apparentSpeedRatio,
      crossover ?? 0,
    ) ||
    variance <= 0 ||
    idealApparentSpeed <= 0
  )
    return invalid("Camera moments cannot be represented at this numerical scale.");
  return {
    kind: "accepted",
    data: Object.freeze({
      variance,
      covariance,
      naiveExpectation,
      idealApparentSpeed,
      measuredApparentSpeed,
      apparentSpeedRatio,
      crossover,
    }),
  };
}

The site’s own reference code for this law, in TypeScript. It computes the numbers above when the page runs it, and it is what the page uses wherever a FrankenSim result is not available.

covarianceEstimator · src/physics/reference/inference/observation.ts · this site’s source, as built · sha256:c819dee82ba95281ed8aad5cd93dc656ca7b3acc11633efc7497f1a710882dab

This function computes the listed outputs when it runs.

In words

Two measured moments, two unknowns. The diffusivity is the variance over two plus the covariance, all over the frame interval; the localization variance is a weighted combination of the two with the exposure entering through the ratio Te / 6 dt. Both come out of one pass over the increments, and neither needs the other declared in advance, which is what the previous function's opposite signs make possible.

Implementation

/** Known-drift moments. Do not silently substitute a fitted mean: centering
 * changes the finite-sample covariance expectation. Channels are independent. */
export function covarianceEstimator(
  increments: Float64Array,
  dt: number,
  { d, exposure, knownDrift = 0 }: { d: number; exposure: number; knownDrift?: number },
): Assessment<Readonly<{ variance: number; covariance: number; D: number; sigma2: number }>> {
  if (
    !(increments instanceof Float64Array) ||
    ![1, 2].includes(d) ||
    increments.length % d ||
    increments.length > 100000 ||
    !finite(dt, exposure, knownDrift) ||
    dt <= 0 ||
    exposure < 0 ||
    exposure > dt ||
    !increments.every(Number.isFinite)
  )
    return invalid(
      "Use finite equally spaced increments, admitted exposure and an explicitly known drift.",
    );
  const M = increments.length / d;
  if (M < 3) return missing("The covariance estimator needs at least three increments.");
  let variance = 0,
    covariance = 0;
  for (let c = 0; c < d; c++)
    for (let i = 0; i < M; i++) {
      const incA = increments[i * d + c];
      if (incA === undefined) continue;
      const a = incA - (c === 0 ? knownDrift * dt : 0);
      variance += (a * a) / (d * M);
      if (i < M - 1) {
        const incB = increments[(i + 1) * d + c];
        if (incB !== undefined) {
          const b = incB - (c === 0 ? knownDrift * dt : 0);
          covariance += (a * b) / (d * (M - 1));
        }
      }
    }
  const R = exposure / (6 * dt),
    D = (variance / 2 + covariance) / dt;
  const sigma2 = R * variance + (2 * R - 1) * covariance;
  if (!finite(variance, covariance, D, sigma2))
    return invalid("The sample moments cannot be represented.");
  // Negative finite-sample estimates are diagnostics, not silently clipped.
  return { kind: "accepted", data: Object.freeze({ variance, covariance, D, sigma2 }) };
}

The site’s own reference code for this law, in TypeScript. It computes the numbers above when the page runs it, and it is what the page uses wherever a FrankenSim result is not available.

disjointPairsKnownNoiseInterval · src/physics/reference/inference/observation.ts · this site’s source, as built · sha256:28f46917a5658571285f63148076eba58dc36a31814a32f4a74b00ccbbf3cf2b

This function computes the listed outputs when it runs.

In words

Take frames in pairs, (0,1), (2,3) and so on, so that no frame and no localization error appears in two increments. Estimate the increment variance from those pairs, subtract twice the noise variance, and divide by twice the interval less a third of the exposure. The interval comes from the chi-square band on the variance, with the noise endpoints subtracted in the widening direction. Drift is fitted from the pairs rather than taken from the truth, which costs a degree of freedom and is the honest choice when a reader has only the frames.

Implementation

/** Disjoint frame pairs (0,1),(2,3),... share neither frames nor noise.
 * Uniform exposures are forward [t_i,t_i+Te], Te <= dt. Their Brownian
 * supports are disjoint too. Constant drift is fitted, not taken from truth.
 * A negative upper endpoint gives an EMPTY physical confidence set, a miss
 * which must be retained in coverage studies, not retried or made positive.
 */
export function disjointPairsKnownNoiseInterval(
  input: Readonly<{
    positions: Float64Array;
    dt: number;
    exposure: number;
    d: number;
    alpha: number;
    noise: { kind: "exact"; sigma2: number } | { kind: "stationary-clicks"; estimate: ClickNoise };
    equalSpacing?: boolean;
    independentNoise?: boolean;
  }>,
): Assessment<PairInterval> {
  const { positions, dt, exposure, d, alpha, noise } = input;
  if (
    !(positions instanceof Float64Array) ||
    ![1, 2].includes(d) ||
    positions.length % d ||
    !finite(dt, exposure, alpha) ||
    dt <= 0 ||
    exposure < 0 ||
    exposure > dt ||
    alpha <= 0 ||
    alpha >= 1 ||
    !positions.every(Number.isFinite)
  )
    return invalid(
      "Use finite frame positions, one or two coordinates, uniform exposure no longer than frame spacing, and valid coverage.",
    );
  if (input.equalSpacing === false || input.independentNoise === false)
    return missing(
      "This pair procedure requires equal spacing and independent Gaussian localization errors.",
    );
  const pairs = Math.floor(positions.length / d / 2);
  if (pairs < 2) return missing("At least two disjoint frame pairs are needed when fitting drift.");
  if (!noise || !["exact", "stationary-clicks"].includes(noise.kind))
    return invalid("Declare exact localization variance or a stationary-click estimate.");
  const sigma2 = noise.kind === "exact" ? noise.sigma2 : noise.estimate?.sigma2;
  if (!Number.isFinite(sigma2) || sigma2 < 0)
    return invalid("Localization variance must be finite and nonnegative.");
  const increments = new Float64Array(pairs * d);
  for (let k = 0; k < pairs; k++)
    for (let c = 0; c < d; c++) {
      const posNext = positions[(2 * k + 1) * d + c];
      const posPrev = positions[2 * k * d + c];
      if (posNext !== undefined && posPrev !== undefined) {
        increments[k * d + c] = posNext - posPrev;
      }
    }
  const e = estimateIncrements(increments, 0.5, d, "drift-centered");
  if (e.kind !== "accepted") return e;
  const estimatedNoise = noise.kind === "stationary-clicks",
    alphaVariance = estimatedNoise ? alpha / 2 : alpha;
  const varianceBand = chiSquareInterval({
    dHat: e.data.unbiasedDHat,
    q: e.data.q,
    alpha: alphaVariance,
    estimatorId: "disjoint-pair-coordinate-variance",
  });
  if (varianceBand.kind !== "accepted") return varianceBand;
  let noiseInterval: readonly [number, number] = [sigma2, sigma2];
  if (noise.kind === "stationary-clicks") {
    const n = noise.estimate;
    if (n.d !== d || !Number.isSafeInteger(n.clicks) || n.clicks < 5 || n.q !== d * (n.clicks - 1))
      return invalid(
        "Stationary-click dimensions and degrees of freedom must match the noise declaration.",
      );
    const band = chiSquareInterval({
      dHat: sigma2,
      q: n.q,
      alpha: alpha / 2,
      estimatorId: "stationary-feature-noise-variance",
    });
    if (band.kind !== "accepted") return band;
    noiseInterval = [band.data.lower, band.data.upper];
  }
  const denominator = 2 * (dt - exposure / 3);
  const rawLower = (varianceBand.data.lower - 2 * noiseInterval[1]) / denominator;
  const upper = (varianceBand.data.upper - 2 * noiseInterval[0]) / denominator;
  const estimate = (e.data.unbiasedDHat - 2 * sigma2) / denominator;
  if (!finite(rawLower, upper, estimate))
    return invalid("The noise-corrected interval cannot be represented.");
  const coverageKind = estimatedNoise ? "conservative" : "exact";
  const interval: StatisticalInterval | null =
    upper < 0
      ? null
      : Object.freeze({
          lower: Math.max(0, rawLower),
          upper,
          q: e.data.q,
          coverage: 1 - alpha,
          uncertaintyKind: "statistical-interval",
          coverageKind,
          estimatorId: estimatedNoise
            ? "disjoint-pairs; stationary clicks; Bonferroni"
            : "disjoint-pairs; exact localization variance",
        });
  return {
    kind: "accepted",
    data: Object.freeze({
      estimate,
      pairs,
      q: e.data.q,
      interval,
      empty: interval === null,
      lowerClipped: rawLower < 0,
      noiseInterval: Object.freeze(noiseInterval),
      coverage: 1 - alpha,
      coverageKind,
    }),
  };
}

The site’s own reference code for this law, in TypeScript. It computes the numbers above when the page runs it, and it is what the page uses wherever a FrankenSim result is not available.

stationaryClickNoiseEstimate · src/physics/reference/inference/observation.ts · this site’s source, as built · sha256:edf6a3b57f9af2fdeb61bce6f389dec306fd70e77885bf772cc1551ecb1c81fb

This function computes the listed outputs when it runs.

In words

It takes at least five clicks per coordinate on a stationary feature and returns the variance of their scatter, with the degrees of freedom that variance carries. That number is then the noise the pair interval subtracts, so the laboratory never has to assume a localization error: a reader supplies one by doing the same thing an experimenter would.

Implementation

export function stationaryClickNoiseEstimate(
  clicks: Float64Array,
  { d }: { d: number },
): Assessment<ClickNoise> {
  if (
    !(clicks instanceof Float64Array) ||
    ![1, 2].includes(d) ||
    clicks.length % d ||
    clicks.length / d < 5
  )
    return invalid("At least five stationary-feature clicks per coordinate are required.");
  const e = estimateIncrements(clicks, 0.5, d, "drift-centered");
  if (e.kind !== "accepted") return e;
  return {
    kind: "accepted",
    data: Object.freeze({ sigma2: e.data.unbiasedDHat, q: e.data.q, clicks: clicks.length / d, d }),
  };
}

The site’s own reference code for this law, in TypeScript. It computes the numbers above when the page runs it, and it is what the page uses wherever a FrankenSim result is not available.

bartlettBandsMA1 · src/physics/reference/inference/observation.ts · this site’s source, as built · sha256:4b2cccf1506e72469656be3250df670885efe5ab2c1c5acc45a0b261d49c03dd

This function computes the listed outputs when it runs.

In words

Bartlett's asymptotic formulas for a moving-average process of order one, which is what camera increments are: correlated with their immediate neighbours and with nothing further away. Given the variance and the neighbour covariance and the number of increments, it returns the standard deviation each of those two moments would show across repeated runs.

Implementation

/** Asymptotic MA(1) sampling bands, NOT confidence intervals for diffusivity. */
export function bartlettBandsMA1(
  gamma0: number,
  gamma1: number,
  M: number,
  d = 1,
): Computation<Readonly<{ sdVariance: number; sdCovariance: number }>> {
  if (
    !finite(gamma0, gamma1) ||
    gamma0 <= 0 ||
    Math.abs(gamma1) > gamma0 / 2 ||
    !Number.isSafeInteger(M) ||
    M < 3 ||
    ![1, 2].includes(d)
  )
    return invalid("Use an admissible MA(1) covariance and at least three increments.");
  const sdVariance = Math.sqrt((2 * (gamma0 ** 2 + 2 * gamma1 ** 2)) / (M * d));
  const sdCovariance = Math.sqrt((gamma0 ** 2 + 3 * gamma1 ** 2) / (M * d));
  if (!finite(sdVariance, sdCovariance) || sdVariance === 0 || sdCovariance === 0)
    return invalid("The sampling band is outside the numerical range.");
  return { kind: "accepted", data: { sdVariance, sdCovariance } };
}

Each placement has its own accepted settings, worker and observations.

Noise-calibration assumption: stationary-feature clicks and the moving particle must have the same localization variance. The clicks cannot establish that assumption.

Not modeled: Non-uniform exposure profiles; Correlated or non-Gaussian localization errors; Pixelation and sub-pixel quantization; Particles leaving the focal plane; Time-varying drift or turbulent flow; Irregular frame timing; Motion outside the measured coordinates; Difference between stationary-feature click noise and moving-particle localization error.

Why these procedures differ

An exposure is an average, not a point

The camera model uses a uniform exposure beginning at each frame time. Its position is the average latent position during that exposure, plus stage drift and independent Gaussian localization error. At zero exposure it uses the instantaneous frame-start position. The generated bridge integrals make these averages consistent with the same Brownian path.

Yi=1Te∫titi+TeX(s) ds+vstage(ti+Te/2)+εi,εi∼N(0,σ2)\begin{gathered}\begin{aligned}Y_i &= \frac{1}{T_e}\int_{t_i}^{t_i+T_e}X(s)\,ds \\ &\quad + v_{\mathrm{stage}}(t_i+T_e/2)+\varepsilon_i,\end{aligned} \\ \varepsilon_i\sim\mathcal N(0,\sigma^2)\end{gathered}

Neighbors are correlated

For uniform, non-overlapping exposures and equally spaced frames, the per-coordinate variance and adjacent covariance of drift-subtracted increments are:

γ0=2D(Δt−Te/3)+2σ2,γ1=DTe/3−σ2\begin{gathered}\gamma_0=2D(\Delta t-T_e/3)+2\sigma^2, \\ \gamma_1=DT_e/3-\sigma^2\end{gathered}

Localization error enters two neighboring displacements with opposite signs. Motion blur contributes the other sign. Ignoring both can understate or overstate diffusion. Subtracting fitted drift alone does not remove either camera effect.

Use covariance, or choose independent pairs

For known drift, the combination of the second moment and neighboring product cancels the localization and blur terms. The covariance point estimate can still be negative in a finite sample; clipping it would conceal a diagnostic.

D^CVE=γ^0/2+γ^1Δt\widehat D_{\mathrm{CVE}}=\frac{\widehat\gamma_0/2+\widehat\gamma_1}{\Delta t}

A different route takes disjoint frame pairs (0,1), (2,3), and so on. No pair shares a position error, and exposure is no longer than the spacing. These pair displacements are independent Gaussian vectors. Fitting one drift per coordinate leaves q = d(K − 1) degrees of freedom for K pairs. A chi-square interval for their variance can therefore be inverted using the known exposure and localization variance.

D=V−2σ22(Δt−Te/3)D=\frac{V-2\sigma^2}{2(\Delta t-T_e/3)}

When localization variance comes from independent stationary-feature clicks, half the allowed error probability goes to its variance interval and half to the pair-variance interval. Combining their endpoints gives a conservative confidence set. The set is intersected with D ≥ 0. An empty intersection is a possible outcome and is retained as a miss, not turned into a positive answer.

Shorter intervals can magnify camera error

With zero exposure and mean drift removed, dividing the coordinate spread by the observation interval gives an apparent speed. Localization error dominates below the noise-only crossover interval σ²/D. This does not reveal a finite instantaneous Brownian velocity.

vapp,ideal=2D/Δt,vapp,camera=2DΔt+2σ2Δt\begin{gathered}v_{\mathrm{app,ideal}}=\sqrt{2D/\Delta t}, \\ v_{\mathrm{app,camera}}=\frac{\sqrt{2D\Delta t+2\sigma^2}}{\Delta t}\end{gathered}

Keep this later model separate from the paper

These are original explanations of modern camera and inference models, not a transcription of Einstein’s argument. Synthetic camera observations are not experimental evidence for a physical suspension. The interval procedures assume constant drift, constant diffusivity, exact timing and calibration, uniform exposure, and independent Gaussian position errors. No irregular or censored data importer is supplied.

Berglund (2010): camera-based single-particle tracking · Vestergaard, Blainey and Flyvbjerg (2014): covariance-based diffusion estimation · Read the uncertainty foundation

Embed this laboratory on another page. It starts from its worked defaults, not your current settings.