Numbers on an inspection line decide pass or fail, so there is a strong pull toward collapsing them into a single figure. The 40 exhibits in this room show what disappears the moment you do: a pooled ROC that hides one defect class’s blind spot in woven fabric, veiling glare that leaves the MTF passing while the black level fails, a barcode decoder that looks better by read rate alone because it never says ‘unreadable’.
Every ground truth is planted: a closed-form periodic background, the laser-profile h(x), the analytic 1-D heat-conduction solution, closed-form bearing defect frequencies. That is what lets each exhibit measure ‘where detection stops working’ instead of ‘detection worked’, without fitting the threshold afterwards.
The other shared feature is that failure arrives as a cliff, not a slope: 15 versus 16 degrees of tilt, a 25-second versus a 4-second fitting window, ΔT of 1.6 K. Most of those positions can be predicted from geometry or physics first, and wherever they could be, the prediction is checked against the measurement.
Barcode tests must distinguish a wrong value from no value instead of counting returned answers alone. A simple code was degraded in four ways; strict reading gave a 7.3 % misread rate. A permissive reader appears more successful because it always returns digits, but creates far more wrong answers.
↑ Where a 1-D Barcode Stops Reading — Counting Misreads and Unreadables Separately ―― A home-made simple code (not a real standard) damaged four ways, with success, misread and unreadable counted as three separate outcomes. Over the 384 images past the onset of damage, the strict decoder that checks run structure misreads 7.3 %, while the lenient decoder that always returns nine digits misreads 46.1 % — and has the higher success rate (47.7 % versus 40.1 %). The tilt cliff is pure geometry (predicted 15.95 degrees) and lands between 15 and 16 degrees.
↑ The measurement ―― 小さい汚れは行が「読めてしまう」ので誤った票を投じる。大きい汚れは棄権するので多数決が効く。 (figure labels are in Japanese; the numbers are the same)
↑ ぼけ 3 本(m=2,3,4 px)は 1 本に重なる。
↑ (d) は走査線が符号の上下からはみ出す角度。
py -3.11 examples/poc_barcode_1d.py
Source: examples/poc_barcode_1d.py
This run produced 4 figures in total - see them all
Ops used (notes): decode_barcode · gen_measure_rectangle2 · measure_pos · vol_edge_probe
On a synthetic micro-CT particle bed, Bruggeman is compared with a numerical reference from steady-state diffusion on the same binary volume under stated boundary conditions. For this family eps=0.4448 gives 1.499 versus 1.843 (-18.6%); sweeping 0.691 to 0.168 grows error -8.2% to -69.1%, with fitted exponents 1.78/2.70. Flattening particles 4:1 raises through-plane tau 1.843 to 6.794 while the rule stays near 1.5. Closed pores reach 1.92% and cannot explain the sweep alone; narrow necks are a candidate without isolated causality. Straight pores give numerical tau=1 versus Bruggeman 2; disconnected pores return no_through_path.
↑ Electrode tortuosity from CT — the rule of thumb only sees porosity ―― On a synthetic micro-CT particle bed, Bruggeman is compared with a numerical reference from steady-state diffusion on the same binary volume under stated boundary conditions. For this family eps=0.4448 gives 1.499 versus 1.843 (-18.6%); sweeping 0.691 to 0.168 grows error -8.2% to -69.1%, with fitted exponents 1.78/2.70. Flattening particles 4:1 raises through-plane tau 1.843 to 6.794 while the rule stays near 1.5. Closed pores reach 1.92% and cannot explain the sweep alone; narrow necks are a candidate without isolated causality. Straight pores give numerical tau=1 versus Bruggeman 2; disconnected pores return no_through_path.
Terms: Bruggeman — An effective-medium relation that approximates transport in a porous material by a power of porosity, often used as a baseline for tortuosity or effective diffusivity.
↑ The measurement ―― 左: 上端 1 / 下端 0 の濃度場。等濃度線が固相を避けて曲がる分が遠回り。右: bond ごとの散逸(明るいほど流れが集中している = 首)。 (figure labels are in Japanese; the numbers are the same)
↑ ε が下がるほど経験則と真値が開く。
↑ この回の図
↑ 経験則は向きを持てない。
↑ 同じ物理構造を粗い格子から細かい格子まで。
py -3.11 examples/poc_battery_electrode_tortuosity.py
Source: examples/poc_battery_electrode_tortuosity.py
This run produced 6 figures in total - see them all
Ops used (notes): intersection · vol_distance_transform · vol_label
Bearing diagnosis needs a limit for finding fault impacts inside noise. A known impulse train was buried until the amplitude trace still detected every trial at -18.4 dB SNR. This processing reaches deeper than raw frequencies, but produces false alarms unless its cutoff comes from fault-free records.
↑ Seven-Level SNR Sweep on Synthetic Bearing Signals — Raw versus Envelope Detection Rates ―― A synthetic impulse train at closed-form BPFO 104.556 Hz is tested at seven SNR levels with ten seeds each. The lowest 10/10 points are -0.9 dB raw and -18.4 dB envelope (17.5 dB apart), empirical coarse-grid points rather than detection limits. Even 10/10 gives a 74.1% one-sided 95% lower bound. On 20 null records independent of threshold selection, false alarms are 0/20 with a 13.9% one-sided 95% upper bound.
↑ The measurement ―― どちらも最悪条件では 0 に落ちる。包絡線は万能ではなく、崖が悪い SNR 側へ動くだけ。 (figure labels are in Japanese; the numbers are the same)
↑ win=32 は毎回共振を含み、win=256 は毎回外す。
py -3.11 examples/poc_bearing_diagnosis.py
Source: examples/poc_bearing_diagnosis.py
This run produced 3 figures in total - see them all
Ops used (notes): bearing_defect_frequencies · envelope_spectrum · spectral_kurtosis · spectrum · synthesize_bearing_signal
Warpage removal can absorb a genuinely low bump into the correction surface. Curves fitted to a seeded pillar field left a 1.23 µm readout error after quadratic correction. Raising the order does not help, and a broad low-order defect vanishes into reported warpage while missed bumps increase.
↑ Bump coplanarity versus substrate warpage — subtract too much and the real defect goes with it ―― A height map of 256 Cu pillars is planted with 50 µm PV warpage, 4 µm 1-sigma pillar-to-pillar spread and six short bumps. The zero point — subtract only a least-squares plane, the JEDEC seating plane — reads the individual deviation with 8.84 µm RMS error (2.2x the real spread), raising 58 false rejects while missing one genuinely short bump. A quadratic reference surface brings it to 1.23 µm with zero false rejects; a cubic one is worse (1.37 µm), because the planted high-order mode is quartic and the four extra terms only absorb real signal. The cliff is predictable from geometry: the quadratic fit degrades to the 4 µm level at 169.9 µm PV (predicted) versus 169.4 µm (measured). Adding a real low-order defect — the centre sagging 8 µm — removes 88.2 % of it from the residual while the short-bump detection count stays at 5 of 6, and misses rise from 0 to 2; the lost signal reappears as warpage growing 30.06 to 36.20 µm.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ 真値の共平面性は 38.44 µm、本当に仕様外なのは 5 本。
↑ 左端の 6 本が仕込んだ短小。
↑ そりの形を固定すれば、取り切れない残りは PV に比例する。
↑ 消えた分はそり側の数字に足されている(+6.14 µm)。
py -3.11 examples/poc_bump_coplanarity.py
Source: examples/poc_bump_coplanarity.py
This run produced 6 figures in total - see them all
Ops used (notes): auto_threshold · background_flatten · blob_features · blob_label · blob_overlay · eval_poly_surface · fit_poly_surface · surface_form_error
Cracks from 0.05 to 2.0 mm wide in a field where 1 px = 0.20 mm, measured by thresholding-and-counting and by integrating the intensity deficit across the crack. Thresholding returns nothing at or below 0.20 mm and reports 0.200 mm for all four true widths between 0.25 and 0.40 mm. Integration tracks continuously down to 0.05 mm (0.25 px), but curved illumination adds a +0.1741 mm offset under a first-order baseline (+0.0062 mm with second order).
↑ Concrete Crack Width Is Thinner Than a Pixel — Counting Width Versus Integrating Width ―― Cracks from 0.05 to 2.0 mm wide in a field where 1 px = 0.20 mm, measured by thresholding-and-counting and by integrating the intensity deficit across the crack. Thresholding returns nothing at or below 0.20 mm and reports 0.200 mm for all four true widths between 0.25 and 0.40 mm. Integration tracks continuously down to 0.05 mm (0.25 px), but curved illumination adds a +0.1741 mm offset under a first-order baseline (+0.0062 mm with second order).
↑ The measurement ―― 2 値化の 2 本は階段。0.20 mm(1 px)以下ではマスクが空になり 0(= 未検出)へ落ちる。積分法は 0.05 mm (0.25 px)まで直線 y=x に乗る。 (figure labels are in Japanese; the numbers are the same)
↑ 点ごとでは 2 値化が下に見えるが、経路平均に直すと積分法の散らばりは消え、2 値化の偏りは残る。
↑ 真の幅は 0.60 mm 固定。
py -3.11 examples/poc_crack_width.py
Source: examples/poc_crack_width.py
This run produced 4 figures in total - see them all
Ops used (notes): distance_transform · sk_medial · skeleton · thinning · vol_distance_transform
Three defect classes (thread break, stain, dim patch) buried in a weave of period 8 px, with the detector’s score map and per-class ROC (true-positive versus false-positive curve) curves side by side. The everyday recipe ‘notch out the grid, remove low frequencies’ pools to an AUC of 0.8113 and looks like a pass, but the dim-patch class alone scores 0.4746, worse than chance. The one line that removes low frequencies was deleting the defect along with the lighting; drop it and the class recovers to 0.9998.
↑ Defects Buried in a Periodic Background — What a Pooled ROC (true-positive versus false-positive curve) Hides ―― Three defect classes (thread break, stain, dim patch) buried in a weave of period 8 px, with the detector’s score map and per-class ROC (true-positive versus false-positive curve) curves side by side. The everyday recipe ‘notch out the grid, remove low frequencies’ pools to an AUC of 0.8113 and looks like a pass, but the dim-patch class alone scores 0.4746, worse than chance. The one line that removes low frequencies was deleting the defect along with the lighting; drop it and the class recovers to 0.9998.
Terms: ROC — A curve of true-positive rate against false-positive rate as the decision threshold varies, showing a binary detector’s trade-off.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ 対角線に乗っている系列が盲点。
↑ 欠陥の無い地だけで測った残差。
py -3.11 examples/poc_fabric_defect.py
Source: examples/poc_fabric_defect.py
This run produced 4 figures in total - see them all
Locating a leak in a 120 m buried main from the arrival-time difference at two sensors, with the source, sound speed, attenuation and reflections all planted as ground truth. With the true sound speed the estimate lands within 0.0107 m at 0 dB SNR (1.2x the Knapp-Carter bound of 0.0091 m); the threshold was predicted at -20.1 dB but measured at -12.5 dB, below which the error jumps to 36.0 m — the whole search window. Yet a 10 % error in the assumed sound speed alone moves the dig by 1.798 m (against the predicted (dc/c)(x-L/2) = 1.800 m), and on a pipe that changes from ductile iron to plastic mid-run the time difference is exactly zero, so iron, plastic or their average all miss by 18.001 m: no calibration of a single speed can fix a factor multiplying zero. The reflection prediction failed — GCC-PHAT beat the plain correlation by only 10 %, because 0.121 m of its 0.121 m RMS is bias, and whitening acts on the magnitude while the delay lives in the phase.
↑ Digging Where the Sound Says — A Clean Correlation Still Digs in the Wrong Place ―― Locating a leak in a 120 m buried main from the arrival-time difference at two sensors, with the source, sound speed, attenuation and reflections all planted as ground truth. With the true sound speed the estimate lands within 0.0107 m at 0 dB SNR (1.2x the Knapp-Carter bound of 0.0091 m); the threshold was predicted at -20.1 dB but measured at -12.5 dB, below which the error jumps to 36.0 m — the whole search window. Yet a 10 % error in the assumed sound speed alone moves the dig by 1.798 m (against the predicted (dc/c)(x-L/2) = 1.800 m), and on a pipe that changes from ductile iron to plastic mid-run the time difference is exactly zero, so iron, plastic or their average all miss by 18.001 m: no calibration of a single speed can fix a factor multiplying zero. The reflection prediction failed — GCC-PHAT beat the plain correlation by only 10 %, because 0.121 m of its 0.121 m RMS is bias, and whitening acts on the magnitude while the delay lives in the phase.
↑ The measurement ―― 同じ漏水源に既知の遅れ 28.80 ms・距離に応じた減衰・独立な広帯域雑音を乗せた。 (figure labels are in Japanese; the numbers are the same)
↑ 探索窓は管路 0-120 m のぶんだけ。
↑ 漏水位置を 41 点振った。
↑ この回の図
↑ 相関の形も相関係数も一切変わらない。
py -3.11 examples/poc_leak_localization.py
Source: examples/poc_leak_localization.py
This run produced 13 figures in total - see them all
Ops used (notes): arrow · bandpass · correlation_score · text_box · transfer_function
Six states of a rotating machine (healthy, misalignment, unbalance, outer-race spall, poor lubrication, looseness) are built from closed-form bearing defect frequencies, the exact steady fin-equation solution for the casing temperature, and a planted shaft offset, then classified from three sensors. Fused accuracy is 100.0 % — but vibration alone is also 100.0 %: thermal and geometry add nothing. Misalignment reaches 100.0 % from any single sensor (correlations with the true severity 0.843 / 0.841 / 0.978 — three faces of one number), while thermal alone can never separate healthy, unbalance and looseness (48/48 confusions inside that trio) and dropping vibration takes them from 100.0 % to 50.0 % / 31.2 %. Fusion only earns its keep once vibration breaks: at noise sigma 1.6 it lifts 45.8 % to 78.1 %. The cliffs were predicted on single features: the 0.5X order bin needs T > 2/f_r = 68.6 ms (measured d’ 2.45 -> 4.32 across the 50 -> 70 ms step), the thermal spread dies past the 66 mm half-diameter (d’ 0.00 at 96 mm pitch). The sideband prediction 1/T < FTF = 86.1 ms was wrong; the peak-reading window condition 1.2/FTF = 103 ms is the right one.
↑ Fusing thermal, vibration and geometry for machine health — three sensors, one fact ―― Six states of a rotating machine (healthy, misalignment, unbalance, outer-race spall, poor lubrication, looseness) are built from closed-form bearing defect frequencies, the exact steady fin-equation solution for the casing temperature, and a planted shaft offset, then classified from three sensors. Fused accuracy is 100.0 % — but vibration alone is also 100.0 %: thermal and geometry add nothing. Misalignment reaches 100.0 % from any single sensor (correlations with the true severity 0.843 / 0.841 / 0.978 — three faces of one number), while thermal alone can never separate healthy, unbalance and looseness (48/48 confusions inside that trio) and dropping vibration takes them from 100.0 % to 50.0 % / 31.2 %. Fusion only earns its keep once vibration breaks: at noise sigma 1.6 it lifts 45.8 % to 78.1 %. The cliffs were predicted on single features: the 0.5X order bin needs T > 2/f_r = 68.6 ms (measured d’ 2.45 -> 4.32 across the 50 -> 70 ms step), the thermal spread dies past the 66 mm half-diameter (d’ 0.00 at 96 mm pitch). The sideband prediction 1/T < FTF = 86.1 ms was wrong; the peak-reading window condition 1.2/FTF = 103 ms is the right one.
↑ The measurement ―― 軸受外輪傷は狭く熱く、潤滑不良は広く熱い。最高温度だけ見ると同じ顔になる。 (figure labels are in Japanese; the numbers are the same)
↑ 芯ずれは 2X、アンバランスは 1X、ゆるみは 0.5X と櫛。
↑ 平行ずれ(切片)と角度ずれ(傾き)を fit_line3 で分けて取る。
↑ 芯ずれと軸受外輪傷は無傷。
↑ 予測は 68.6 ms(0.5X の次数ビン)と 86.1 ms(FTF 側帯波)。
py -3.11 examples/poc_machine_condition_fusion.py
Source: examples/poc_machine_condition_fusion.py
This run produced 13 figures in total - see them all
Ops used (notes): angle_between_lines · arrow · bearing_defect_frequencies · blob_label · distance_point_line · ellipse · fuse · jitter · mat_pinv · rounded_rect · spectrum · stat_correlation · synthesize_bearing_signal · text_box
A code with QR-style layout and random data bits (no error correction), damaged by blur, tilt and occlusion, with the raw bit error rate counted directly. The nulls sit at chance (0.526 / 0.507) while the reader scores 0.0000. Cliffs: 78 degrees of tilt, two modules of finder-pattern occlusion; run beside the condition that receives the true homography, localisation is always the stage that fails first.
↑ Reading a Binary Matrix Code — Geometry Always Dies First ―― A code with QR-style layout and random data bits (no error correction), damaged by blur, tilt and occlusion, with the raw bit error rate counted directly. The nulls sit at chance (0.526 / 0.507) while the reader scores 0.0000. Cliffs: 78 degrees of tilt, two modules of finder-pattern occlusion; run beside the condition that receives the true homography, localisation is always the stage that fails first.
↑ The measurement ―― 自力検出の線は sigma/m 0.50 を最後に途切れる(0.60 では位置検出パターンが見つからない)。標本化はそこでまだ BER 0.07 で読めている。 (figure labels are in Japanese; the numbers are the same)
↑ 照明ムラ 0.9 固定。
↑ 位置検出パターンは一辺 2 モジュール(全体の 0.6 %)で致命的。
py -3.11 examples/poc_matrix_code_reading.py
Source: examples/poc_matrix_code_reading.py
This run produced 4 figures in total - see them all
Ops used (notes): adaptive_gauss_thresh · corner_response · illuminate · image_to_world_plane · otsu · sk_sauvola
Moire (beat fringe between periodic patterns) from the interference of the display’s stripes with the camera’s pixel grid, overlaid on genuine luminance non-uniformity in one image. At a smoothing σ of 8 px the total error is +0.9 %, made of +8.3 % moire leakage cancelling -7.4 % attenuation of the real defect. At k = 0.67 the fundamental beat looks safe, yet the third harmonic lands in the defect band and the leakage reaches +202.6 % of the true value.
↑ Can Display-Inspection Moire (beat fringe between periodic patterns) Be Told From Real Non-Uniformity? ―― Moire (beat fringe between periodic patterns) from the interference of the display’s stripes with the camera’s pixel grid, overlaid on genuine luminance non-uniformity in one image. At a smoothing σ of 8 px the total error is +0.9 %, made of +8.3 % moire leakage cancelling -7.4 % attenuation of the real defect. At k = 0.67 the fundamental beat looks safe, yet the third harmonic lands in the defect band and the leakage reaches +202.6 % of the true value.
Terms: Moiré — A long-period fringe produced at the difference spatial frequency when two patterns with slightly different pitch or orientation are superposed.
↑ The measurement ―― σ≈8 px で漏れと減衰が釣り合う。合計だけ見ると「良い測り方」に見える。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 縦線がムラの周波数。
py -3.11 examples/poc_moire_screen.py
Source: examples/poc_moire_screen.py
This run produced 4 figures in total - see them all
Ops used (notes): background_flatten · fft_image · gauss_image · halftone_moire_period · lattice_moire_peak · moire_differential_shift · moire_magnification · moire_mark_read · moire_overlay_image · moire_phase · moire_phase_crlb · moire_phase_to_shift · moire_talbot_gap · reflect
Measuring print misregistration from the printed sheet. Four CMYK plates carry the conventional screen angles (C 15, M 75, Y 0, K 45 degrees) at 133 lpi scanned at 1200 dpi, giving a screen pitch of 9.02 px, and each plate is displaced by a known amount. The central claim is geometry: a halftone is a lattice, so correlation recovers not the displacement d but d modulo the screen lattice. Along the lattice axes that limit is p/2 = 4.51 px and along the diagonal p/sqrt(2) = 6.38 px; beyond it the measurement folds back. Stated in closed form before measuring, it matches at 144 of 148 points (four plates by 37 sweep positions) to within 0.1096 px. The four exceptions are ties on the boundary of the fundamental cell, with at most 0.165 px of margin, and reducing modulo the lattice makes every point agree: the estimator is not wrong, it returns one of the lattice-equivalent answers, and two independent estimators fold identically. The consequence is concrete. A true displacement of 9.64 px, 4.8 times the 2.0 px tolerance, appears as 1.09 to 3.32 px depending only on the screen angle, so two of the four plates look like they pass — from one sheet displaced by one amount. The null baseline, the centroid of ink, does not fold but loses its scale: the slope follows the closed form k = 1 - beta, where beta is the ink of the flat background over the total ink, predicted 0.3983 against 0.3711 measured. One effect was not predicted at all: a 0.9864 px ripple at the screen pitch, caused by rows of dots entering and leaving at the window edge, which a tapered-window control drops to 0.0048 px. Switching to an FM (stochastic) screen removes the folding entirely, with an error of at most 0.0029 px across the whole sweep, which places the blame on the periodicity of the AM screen rather than on the estimator, at the cost of 0.91 times the contrast. A window containing a registration mark reads correctly to 0.0823 px, but with 0.600 % paper stretch and 0.120 degrees of plate rotation the error grows linearly with distance at 0.006319 px per px and exceeds tolerance from 316.5 px predicted, 322.5 px measured — 44 % of the sheet. Two rulers reverse the ranking: raw correlation is the most accurate at 0.0071 px yet agrees with the pass/fail verdict only 67.6 % of the time, while low-pass filtering agrees 89.2 % of the time at 94 times the error; a two-stage estimator that picks the representative with the blunt method and refines with the sharp one wins both, and it fails exactly at the points where the coarse error exceeded the cell radius of 4.511 px. The synthesiser itself was a trap: at 150 lpi and 1200 dpi the pitch is exactly 8.00 px, every dot samples in phase, and the centroid jumps 1.4 px for each 0.5 px of displacement — avoiding the integer ratio is the fix, not supersampling. Finally, an operator used outside its domain failed confidently: fs.frame_align returned inlier_ratio 1.00 and rms_px 0.645 while missing the true (0.00, +1.30) by 80.85 px, and the answer was not even a lattice vector. That finding was fixed in the operator: its docstring now records the measurement, and the vote histogram’s runner-up over winner is returned as vote_margin — 1.000 on this halftone against 0.143 on a star field, where the agreement ratio is 1.00 in both.
↑ Print Misregistration from the Sheet — A Halftone Is a Lattice, So the Answer Is Not Unique ―― Measuring print misregistration from the printed sheet. Four CMYK plates carry the conventional screen angles (C 15, M 75, Y 0, K 45 degrees) at 133 lpi scanned at 1200 dpi, giving a screen pitch of 9.02 px, and each plate is displaced by a known amount. The central claim is geometry: a halftone is a lattice, so correlation recovers not the displacement d but d modulo the screen lattice. Along the lattice axes that limit is p/2 = 4.51 px and along the diagonal p/sqrt(2) = 6.38 px; beyond it the measurement folds back. Stated in closed form before measuring, it matches at 144 of 148 points (four plates by 37 sweep positions) to within 0.1096 px. The four exceptions are ties on the boundary of the fundamental cell, with at most 0.165 px of margin, and reducing modulo the lattice makes every point agree: the estimator is not wrong, it returns one of the lattice-equivalent answers, and two independent estimators fold identically. The consequence is concrete. A true displacement of 9.64 px, 4.8 times the 2.0 px tolerance, appears as 1.09 to 3.32 px depending only on the screen angle, so two of the four plates look like they pass — from one sheet displaced by one amount. The null baseline, the centroid of ink, does not fold but loses its scale: the slope follows the closed form k = 1 - beta, where beta is the ink of the flat background over the total ink, predicted 0.3983 against 0.3711 measured. One effect was not predicted at all: a 0.9864 px ripple at the screen pitch, caused by rows of dots entering and leaving at the window edge, which a tapered-window control drops to 0.0048 px. Switching to an FM (stochastic) screen removes the folding entirely, with an error of at most 0.0029 px across the whole sweep, which places the blame on the periodicity of the AM screen rather than on the estimator, at the cost of 0.91 times the contrast. A window containing a registration mark reads correctly to 0.0823 px, but with 0.600 % paper stretch and 0.120 degrees of plate rotation the error grows linearly with distance at 0.006319 px per px and exceeds tolerance from 316.5 px predicted, 322.5 px measured — 44 % of the sheet. Two rulers reverse the ranking: raw correlation is the most accurate at 0.0071 px yet agrees with the pass/fail verdict only 67.6 % of the time, while low-pass filtering agrees 89.2 % of the time at 94 times the error; a two-stage estimator that picks the representative with the blunt method and refines with the sharp one wins both, and it fails exactly at the points where the coarse error exceeded the cell radius of 4.511 px. The synthesiser itself was a trap: at 150 lpi and 1200 dpi the pitch is exactly 8.00 px, every dot samples in phase, and the centroid jumps 1.4 px for each 0.5 px of displacement — avoiding the integer ratio is the fix, not supersampling. Finally, an operator used outside its domain failed confidently: fs.frame_align returned inlier_ratio 1.00 and rms_px 0.645 while missing the true (0.00, +1.30) by 80.85 px, and the answer was not even a lattice vector. That finding was fixed in the operator: its docstring now records the measurement, and the vote histogram’s runner-up over winner is returned as vote_margin — 1.000 on this halftone against 0.143 on a star field, where the agreement ratio is 1.00 in both.
↑ The measurement ―― スクリーン角が違うので格子の向きが違う。ピッチはどれも 9.02 px。同じ物理的なずれでも、この格子の違いが「見かけのずれ」を版ごとに変える(5 節) (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ スクリーン角が違えば格子も違う。
↑ 絵柄も推定器も同じ。
↑ 十字は非周期なので相関のピークが 1 つに決まる。
py -3.11 examples/poc_print_registration.py
Source: examples/poc_print_registration.py
This run produced 13 figures in total - see them all
Ops used (notes): fly_hex_lattice · fly_vogel_lattice · frame_align · halftone_moire_period · halftone_screen · lattice_moire_peak · peak_subbin · piv_cross_correlate
The usual way to estimate “how faint a defect can we still catch” is to measure the limit on a synthetic image: a flat field plus white noise. This exhibit measures how optimistic that estimate is, while keeping an exact ground truth. The backgrounds are three CC0 photographs (brick / grass / gravel); what is planted into them is a Gaussian defect of known position, width and amplitude. The control is a synthetic field whose residual sigma — the variation the detector actually sees — is matched to the real one, so the amount of noise is equal and only structure differs. The verdict is the smallest amplitude at which the planted position becomes the peak of the response; no threshold is involved, so per-image normalisation inside an operator cannot skew it. Matching the noise is not enough: the real backgrounds raise the floor by 2.03x to 3.47x. What sets the floor is structure, not noise. The gap survives all 18 knob settings (three background windows x two defect sizes x three grounds, 1.72x-4.56x) and both detectors — a matched filter and fullseye’s laplace_of_gauss. A prediction that failed: “the gap widens for larger defects” turned out to be an artefact of the amplitude grid — on 34 steps the two sizes round to different grid points, on 60 steps both land at 3.30x. The grid is a knob too. “Real data is position-dependent” is also wrong: the spread of the floor across positions is 16.8x on brick but 2.4x on grass and 3.3x on gravel, indistinguishable from the 3.3x of the matched synthetic control. What creates the spread is the mortar lattice — a structure — not the fact that the image is a photograph. And the null model wins on a single metric. Taking the brightest raw pixel, with no background removed, localises the defect at 0.65x-0.90x the amplitude the matched filter needs, because the matched filter amplifies the background’s structure too; false alarms on a defect-free surface tie at 0 on brick. The two separate the moment the lamp drifts by 2 %: the null model fires 10.3 times per 10,000 pixels while the matched filter stays at 0.0, because a raw-pixel threshold is an absolute brightness. Count localisation, false alarms and drift separately, or a useless detector can be made to win.
↑ How Faint a Defect Can Still Be Found — Planting a Known Truth in a Real Background ―― The usual way to estimate “how faint a defect can we still catch” is to measure the limit on a synthetic image: a flat field plus white noise. This exhibit measures how optimistic that estimate is, while keeping an exact ground truth. The backgrounds are three CC0 photographs (brick / grass / gravel); what is planted into them is a Gaussian defect of known position, width and amplitude. The control is a synthetic field whose residual sigma — the variation the detector actually sees — is matched to the real one, so the amount of noise is equal and only structure differs. The verdict is the smallest amplitude at which the planted position becomes the peak of the response; no threshold is involved, so per-image normalisation inside an operator cannot skew it. Matching the noise is not enough: the real backgrounds raise the floor by 2.03x to 3.47x. What sets the floor is structure, not noise. The gap survives all 18 knob settings (three background windows x two defect sizes x three grounds, 1.72x-4.56x) and both detectors — a matched filter and fullseye’s laplace_of_gauss. A prediction that failed: “the gap widens for larger defects” turned out to be an artefact of the amplitude grid — on 34 steps the two sizes round to different grid points, on 60 steps both land at 3.30x. The grid is a knob too. “Real data is position-dependent” is also wrong: the spread of the floor across positions is 16.8x on brick but 2.4x on grass and 3.3x on gravel, indistinguishable from the 3.3x of the matched synthetic control. What creates the spread is the mortar lattice — a structure — not the fact that the image is a photograph. And the null model wins on a single metric. Taking the brightest raw pixel, with no background removed, localises the defect at 0.65x-0.90x the amplitude the matched filter needs, because the matched filter amplifies the background’s structure too; false alarms on a defect-free surface tie at 0 on brick. The two separate the moment the lamp drifts by 2 %: the null model fires 10.3 times per 10,000 pixels while the matched filter stays at 0.0, because a raw-pixel threshold is an absolute brightness. Count localisation, false alarms and drift separately, or a useless detector can be made to win.
↑ The measurement ―― 同じ欠陥を振幅 0.02 から 1.20 まで上げていく。左=実写の brick、右=残差 σ を揃えた合成の地。雑音の量は同じなのに、右のほうが先に見えてくる。 (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_real_defect_floor.py
Source: examples/poc_real_defect_floor.py
This run produced 2 figures in total - see them all
Ops used (notes): annotate_inset · annotate_legend · laplace_of_gauss · reflect
Three real textures (brick, grass, gravel, all CC0) rotated by known angles, measuring how far the descriptor drifts from itself. The benchmark is set before measuring: the smallest distance between materials, 0.01250 between grass and gravel, is the resolution of the task, and any rotation drift beyond it means a rotated sample is further from itself than from a different material. The anisotropic brick drifts 0.0433 at five degrees and 0.1205 at sixty, 9.6 times the resolution, while grass and gravel stay at 0.0005 to 0.0024, effectively invariant. Two controls isolate the cause: interpolation alone, measured by rotating plus seven and back minus seven degrees, contributes 0.03685 for brick, and an exact ninety-degree rotation with no interpolation at all still gives 0.08501, or 6.8 times the resolution. Anisotropy can be measured independently and explains the ranking: the global concentration of gradient orientation is 0.309 for brick against 0.027 and 0.029 for the others. Switching to rotation-invariant encodings brings brick from 9.64 down through 5.49 to 1.72 — never below one — while the isotropic pair improves tenfold, from 0.17 to 0.02. LBP’s rotation invariance applies to the cyclic shift of a local pattern; it cannot remove the orientation distribution of the material itself.
↑ Rotating Real Textures — Rotation Invariance Holds Only Where It Is Not Needed ―― Three real textures (brick, grass, gravel, all CC0) rotated by known angles, measuring how far the descriptor drifts from itself. The benchmark is set before measuring: the smallest distance between materials, 0.01250 between grass and gravel, is the resolution of the task, and any rotation drift beyond it means a rotated sample is further from itself than from a different material. The anisotropic brick drifts 0.0433 at five degrees and 0.1205 at sixty, 9.6 times the resolution, while grass and gravel stay at 0.0005 to 0.0024, effectively invariant. Two controls isolate the cause: interpolation alone, measured by rotating plus seven and back minus seven degrees, contributes 0.03685 for brick, and an exact ninety-degree rotation with no interpolation at all still gives 0.08501, or 6.8 times the resolution. Anisotropy can be measured independently and explains the ranking: the global concentration of gradient orientation is 0.309 for brick against 0.027 and 0.029 for the others. Switching to rotation-invariant encodings brings brick from 9.64 down through 5.49 to 1.72 — never below one — while the isotropic pair improves tenfold, from 0.17 to 0.02. LBP’s rotation invariance applies to the cyclic shift of a local pattern; it cannot remove the orientation distribution of the material itself.
↑ The measurement ―― brick だけが 1 を大きく超える(最大 9.6 倍)。 (figure labels are in Japanese; the numbers are the same)
↑ 等方な 2 つは 10 倍良くなる。
↑ 単位はどれも「分解能に対する比」。
py -3.11 examples/poc_real_texture_invariance.py
Source: examples/poc_real_texture_invariance.py
This run produced 4 figures in total - see them all
Ops used (notes): cooc_feature_matrix · reflect · sk_lbp
Fragments on a belt carry material, dirt, wetness, tilt and occlusion in known amounts, imaged in 64 SWIR (short-wave infrared) bands. Per-fragment degradation is closed form, s(l)=gR(l)exp(-wA_w(l))+(au(l)+c), so invariance can be predicted before measuring: the spectral angle is invariant to the multiplicative g (0.995 to 0.990 as dirt goes 0 to 0.8; 0.993 at 70 degrees of tilt), and a second derivative annihilates the linear baseline (raw SAM falls 0.995 to 0.827 under an additive baseline while the derivative stays at 0.995 at every level). Two predictions failed: wetness is largely removed by the second derivative (0.818 against 0.282 for raw SAM) because a second derivative weights Gaussian bands by 1/sigma^2 ((43/70)^2 = 0.37), and continuum removal beats raw SAM while the baseline is weak (0.983 vs 0.865) but loses once it is strong (0.736 vs 0.827). Featureless metal vanishes under differentiation (recall 0.06) and a flatness gate restores it to 0.98.
↑ Sorting mixed waste by material — what a preprocessor can erase is decided by algebra ―― Fragments on a belt carry material, dirt, wetness, tilt and occlusion in known amounts, imaged in 64 SWIR (short-wave infrared) bands. Per-fragment degradation is closed form, s(l)=gR(l)exp(-wA_w(l))+(au(l)+c), so invariance can be predicted before measuring: the spectral angle is invariant to the multiplicative g (0.995 to 0.990 as dirt goes 0 to 0.8; 0.993 at 70 degrees of tilt), and a second derivative annihilates the linear baseline (raw SAM falls 0.995 to 0.827 under an additive baseline while the derivative stays at 0.995 at every level). Two predictions failed: wetness is largely removed by the second derivative (0.818 against 0.282 for raw SAM) because a second derivative weights Gaussian bands by 1/sigma^2 ((43/70)^2 = 0.37), and continuum removal beats raw SAM while the baseline is weak (0.983 vs 0.865) but loses once it is strong (0.736 vs 0.827). Featureless metal vanishes under differentiation (recall 0.06) and a flatness gate restores it to 0.98.
Terms: SWIR — The short-wave infrared band, roughly around 1 to 3 µm, where material absorption can distinguish objects that look alike in visible light.; SAM — A classifier that compares spectra by the angle between their vectors, making it invariant to a common multiplicative scale.
↑ The measurement ―― PP と PE は骨格が同じ(-CH2-)なのでわざと似せてある。 (figure labels are in Japanese; the numbers are the same)
↑ 乗算汚れ・傾き・重なりは平ら(SAM は明るさに不変)。
↑ この回の図
↑ 1 個の正解率には出ない盲点がここに出る。
↑ この回の図
py -3.11 examples/poc_recycling_sorting.py
Source: examples/poc_recycling_sorting.py
This run produced 14 figures in total - see them all
Ops used (notes): overlay_labels · spectrum
A crystalline-silicon cell (100 fingers, 3 busbars, 70 grains) is rendered in closed form as an EL image with two electrically isolated regions (true inactive area 4.77 %), 5 cracks and 8 finger interruptions, then imaged through cos^4 vignetting and photon noise. The zero-point global threshold reports a dark-pixel fraction of 21.7 % as the inactive area, but 42 % of those pixels are fingers and busbars and 33 % are grains and vignetting; only 20 % are truly inactive. Dividing out the grid with row/column profiles and gating each defect type separately gives 4.61 % (-0.15 points), crack recall 0.88–1.00 and 8/8 interruptions. Because sk_frangi normalises by the per-image maximum, a calibration line only fixes the scale if it is the strongest ridge in the image (a line like the real cracks responds 0.69, a 3 px black line 1.00); without calibration the defect-free cell produces 147 px of false cracks. From grain contrast c=0.24 false cracks and swallowed interruption bands start together, and the crack-width cliff at 1.25 px follows the linear width-times-depth rule (predicted 1.38 px).
↑ Power loss from solar-cell EL images — dark is not the same as inactive ―― A crystalline-silicon cell (100 fingers, 3 busbars, 70 grains) is rendered in closed form as an EL image with two electrically isolated regions (true inactive area 4.77 %), 5 cracks and 8 finger interruptions, then imaged through cos^4 vignetting and photon noise. The zero-point global threshold reports a dark-pixel fraction of 21.7 % as the inactive area, but 42 % of those pixels are fingers and busbars and 33 % are grains and vignetting; only 20 % are truly inactive. Dividing out the grid with row/column profiles and gating each defect type separately gives 4.61 % (-0.15 points), crack recall 0.88–1.00 and 8/8 interruptions. Because sk_frangi normalises by the per-image maximum, a calibration line only fixes the scale if it is the strongest ridge in the image (a line like the real cracks responds 0.69, a 3 px black line 1.00); without calibration the defect-free cell produces 147 px of false cracks. From grain contrast c=0.24 false cracks and swallowed interruption bands start together, and the crack-width cliff at 1.25 px follows the linear width-times-depth rule (predicted 1.38 px).
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ 孤立領域 2 つ・クラック 5 本・断線 8 本。
↑ この回の図
↑ この回の図
↑ この回の図
py -3.11 examples/poc_solar_el_inspection.py
Source: examples/poc_solar_el_inspection.py
This run produced 8 figures in total - see them all
Ops used (notes): aug_vignette · blob_features · blob_label · blob_select · gaussian · gray_closing · hysteresis_threshold · lines_gauss · otsu · sk_frangi · sk_skeleton · total_length · vignette
A 1608 chip’s pads and terminations carry circular-arc fillets fixed by contact angle and cross-section area; three ring lights at different elevations (red 30-40°, green 15-30°, blue 0-15° of surface tilt) are integrated over a GGX (microfacet reflection model) lobe to synthesise the AOI image, and joints are graded good / insufficient / bridge / tombstone. With an 18° contact angle the concave arc rises to 72° at the wall, so even the lowest ring sees only 28.8 % of the height (predicted): naively integrating the band tilts recovers 0.284 of the true height. Extrapolating the arc to the wall from the positions where the colour changes lands at +1.7 % ± 5.3 % for free arcs, but once extra solder pins the toe at the pad edge it drifts to -42.3 %. A 0.16 mm placement offset steepens the toe past 30°, the green band vanishes and a good joint is called insufficient although its true height has risen (predicted 0.16 mm; the truth only fails at 0.28 mm). Surface roughness, against expectation, does not move the dark edge; it breaks at roughness 0.5 together with the loss of the red band and at 0.6 the whole fillet drops below the dark threshold. The zero point (Lab distance of the pad mean colour) is 100 % right under reference conditions yet flags 56 % of good joints and 68 % of bridges once offset, roughness and lighting vary — no discrimination — while the arc-based grading scores good 98 % / insufficient 98 % / bridge 100 % / tombstone 88 %, the misses all being lifts of 8.7-11.0°.
↑ Solder fillet AOI — three ring lights are a 3-level tilt quantiser, and 70 % of the fillet height sits in the dark ―― A 1608 chip’s pads and terminations carry circular-arc fillets fixed by contact angle and cross-section area; three ring lights at different elevations (red 30-40°, green 15-30°, blue 0-15° of surface tilt) are integrated over a GGX (microfacet reflection model) lobe to synthesise the AOI image, and joints are graded good / insufficient / bridge / tombstone. With an 18° contact angle the concave arc rises to 72° at the wall, so even the lowest ring sees only 28.8 % of the height (predicted): naively integrating the band tilts recovers 0.284 of the true height. Extrapolating the arc to the wall from the positions where the colour changes lands at +1.7 % ± 5.3 % for free arcs, but once extra solder pins the toe at the pad edge it drifts to -42.3 %. A 0.16 mm placement offset steepens the toe past 30°, the green band vanishes and a good joint is called insufficient although its true height has risen (predicted 0.16 mm; the truth only fails at 0.28 mm). Surface roughness, against expectation, does not move the dark edge; it breaks at roughness 0.5 together with the loss of the red band and at 0.6 the whole fillet drops below the dark threshold. The zero point (Lab distance of the pad mean colour) is 100 % right under reference conditions yet flags 56 % of good joints and 68 % of bridges once offset, roughness and lighting vary — no discrimination — while the arc-based grading scores good 98 % / insufficient 98 % / bridge 100 % / tombstone 88 %, the misses all being lifts of 8.7-11.0°.
Terms: GGX — A microfacet reflection model using the long-tailed Trowbridge–Reitz distribution of facet normals.
↑ The measurement ―― 鏡面なら窓の端が階段になる。傾き 40° を超えるとどのリングも届かない。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ E1 は 0.28 倍の直線に乗る(暗部を見ていない)。
↑ この回の図
↑ この回の図
py -3.11 examples/poc_solder_fillet_aoi.py
Source: examples/poc_solder_fillet_aoi.py
This run produced 12 figures in total - see them all
Ops used (notes): access_channel · blob_features · blob_label · blob_select_largest · brdf_microfacet · illumination_design · intensity · rgb_to_lab · trans_from_rgb
A series of dimensions and defect counts measured by machine vision, fed through four SPC ops that use no learning at all (each a textbook closed-form with an exact identity to check against). The Xbar-R chart catches a +4σ shift planted at the tail across 4 subgroups at once (n=5 constants A2/D3/D4 = 0.577/0.000/2.115, ISO 8258); CUSUM catches a +0.8σ sustained drift that Shewhart 3σ never flags, at #45 (right after the drift starts). Process capability separates “capable” from “capable at the current centre”: centred on the spec midpoint Cpk = Cp = 1.307, shifted by +1 Cpk = 0.981 < Cp. Multivariate T² folds the simultaneous drift of three correlated measurements into one verdict (UCL from the F-distribution). As with image restoration, a process running and a process in control are measured separately.
↑ Is the Process in Control, and Is It Capable? — Statistical Process Control from Closed-Form Alone ―― A series of dimensions and defect counts measured by machine vision, fed through four SPC ops that use no learning at all (each a textbook closed-form with an exact identity to check against). The Xbar-R chart catches a +4σ shift planted at the tail across 4 subgroups at once (n=5 constants A2/D3/D4 = 0.577/0.000/2.115, ISO 8258); CUSUM catches a +0.8σ sustained drift that Shewhart 3σ never flags, at #45 (right after the drift starts). Process capability separates “capable” from “capable at the current centre”: centred on the spec midpoint Cpk = Cp = 1.307, shifted by +1 Cpk = 0.981 < Cp. Multivariate T² folds the simultaneous drift of three correlated measurements into one verdict (UCL from the F-distribution). As with image restoration, a process running and a process in control are measured separately.
Terms: Xbar-R — A paired control-chart method that tracks subgroup means and ranges to monitor process location and spread.; CUSUM — A control chart that accumulates deviations from a reference value to detect small sustained shifts in the mean.; Shewhart — A control chart that compares a process statistic with a center line and control limits to flag changes unlikely under common-cause variation.
↑ The measurement ―― Shewhart 3σ は 0 件、CUSUM は #46(ドリフト開始の直後)で h=5 を超えて警報。 (figure labels are in Japanese; the numbers are the same)
↑ EWMA も #46 で管理限界を越えて警報 —— Shewhart 3σ が見逃す小シフトを CUSUM と同様に捕らえる(限界は最初の数点で漸近値へ広がる)。
↑ 末尾の行はどの特徴も単独では 3σ 内(当たり 0 件)なのに MD=7.78 —— 正常行の最大 1.10 に対して 7.1 倍。
py -3.11 examples/poc_spc.py
Source: examples/poc_spc.py
This run produced 4 figures in total - see them all
Ops used (notes): spc_capability · spc_cusum · spc_ewma · spc_hotelling_t2 · spc_mt_distance · spc_mt_sn_ratio · spc_mt_unit_space · spc_xbar_r
| The 20 features (vibration, thermal, shape) of the machine-condition PoC: 48 healthy records form a Mahalanobis–Taguchi unit space, and 32 further healthy records set both thresholds — univariate max | z | and MD — at zero false alarms. Healthy machines have strongly correlated features (coupling vs. global temperature: ρ=0.999), so a record that breaks the correlation is far in distance while every single feature stays in range. Of the 169 fault records the univariate rule called “all features normal”, the MT method flags 79 (47 %); the upper-left region of the scatter (left of the univariate threshold, above the MD threshold) is exactly that set. Two exact identities: the unit space’s mean MD² equals (N−1)/N (from the ddof=1 definition), and for two features the point (+z, −z) has MD² = z²/(1−ρ) in closed form. The customary | z | >3 fires by chance 12.5 % of the time with 20 features. Mild faults are a linear blend between normal (0) and fault (1) — the original PoC’s “severity × fault value” produced, at the mild end, a machine quieter and cooler than normal, and detection was not monotone in severity. Synthetic material; the fraction is an upper bound for this correlation structure. |
| *↑ Every Sensor Reads Normal, Yet the Machine Is Failing — How Much the MT Method Recovers from Univariate 3σ ―― The 20 features (vibration, thermal, shape) of the machine-condition PoC: 48 healthy records form a Mahalanobis–Taguchi unit space, and 32 further healthy records set both thresholds — univariate max | z | and MD — at zero false alarms. Healthy machines have strongly correlated features (coupling vs. global temperature: ρ=0.999), so a record that breaks the correlation is far in distance while every single feature stays in range. Of the 169 fault records the univariate rule called “all features normal”, the MT method flags 79 (47 %); the upper-left region of the scatter (left of the univariate threshold, above the MD threshold) is exactly that set. Two exact identities: the unit space’s mean MD² equals (N−1)/N (from the ddof=1 definition), and for two features the point (+z, −z) has MD² = z²/(1−ρ) in closed form. The customary | z | >3 fires by chance 12.5 % of the time with 20 features. Mild faults are a linear blend between normal (0) and fault (1) — the original PoC’s “severity × fault value” produced, at the mild end, a machine quieter and cooler than normal, and detection was not monotone in severity. Synthetic material; the fraction is an upper bound for this correlation structure.* |
↑ The measurement ―― 芯ずれ。差が最大なのは重症度 0.05 で、単変量 25.0 % に対し MT 法 100.0 %。重症度 0.50 以上は両方式とも 100 %。閾値は健全の別標本 32 本で両方式とも誤警報 0。 (figure labels are in Japanese; the numbers are the same)
↑ アンバランス。
↑ 軸受外輪傷。
↑ 潤滑不良。
↑ ゆるみ。
py -3.11 examples/poc_mt_hidden_fault.py
Source: examples/poc_mt_hidden_fault.py
This run produced 6 figures in total - see them all
Ops used (notes): spc_mt_distance · spc_mt_unit_space
fullseye stopped at OCR pre-processing and had no layer answering “where is the text”. Recognisers (trained models) stay out by design, so the geometry of text alone — a stroke has nearly constant width — drives a classical detector (Stroke Width Transform, Epshtein 2010) in three ops, graded on text we rendered ourselves (ink pixels known to the pixel). Three truths: for a rectangular stroke of width w the SWT equals w at every pixel (edges are defined as the inner boundary pixels on the ink side and width = centre-to-centre distance of opposing boundary pixels + 1, which makes it an exact integer); doubling the image doubles the median from 5 to 10; and the fraction of drawn ink that falls inside candidate boxes (recall). Latin text at size ≥ 32 reaches 80–90 % ink recall, CJK (kanji/kana) 60–79 %, box precision about 50 % (boxes include the glyphs’ white space). A 6 sizes × 4 noise levels table shows that what breaks detection is small text, not noise (the dip at size 16 below size 12 is printed as unexplained). Without a CJK font the example runs Latin only and says so.
↑ Where Is the Text, Without a Recogniser — Grading a Stroke-Width Detector on Text We Drew Ourselves ―― fullseye stopped at OCR pre-processing and had no layer answering “where is the text”. Recognisers (trained models) stay out by design, so the geometry of text alone — a stroke has nearly constant width — drives a classical detector (Stroke Width Transform, Epshtein 2010) in three ops, graded on text we rendered ourselves (ink pixels known to the pixel). Three truths: for a rectangular stroke of width w the SWT equals w at every pixel (edges are defined as the inner boundary pixels on the ink side and width = centre-to-centre distance of opposing boundary pixels + 1, which makes it an exact integer); doubling the image doubles the median from 5 to 10; and the fraction of drawn ink that falls inside candidate boxes (recall). Latin text at size ≥ 32 reaches 80–90 % ink recall, CJK (kanji/kana) 60–79 %, box precision about 50 % (boxes include the glyphs’ white space). A 6 sizes × 4 noise levels table shows that what breaks detection is small text, not noise (the dip at size 16 below size 12 is printed as unexplained). Without a CJK font the example runs Latin only and says so.
↑ The measurement ―― 真値は描いたインク画素。再現率 = インクのうち候補矩形に入った割合。 (figure labels are in Japanese; the numbers are the same)
↑ 真値は描いたインク画素。
py -3.11 examples/poc_text_region_truth.py
Source: examples/poc_text_region_truth.py
This run produced 3 figures in total - see them all
Ops used (notes): swt_map · text_candidates · text_lines
A 40 mm square measured by a camera whose focal length, mount and principal point drift with temperature ΔT, the error split into a + b·R in image radius. At ΔT = 15 K the constant term is +224.5 ppm (the single-cause control gives +225.3 ppm). Drift rises above the noise floor (23 ppm for a 25-frame average) only from ΔT = 1.6 K; below that, the honest report is that no temperature effect is visible.
↑ How Much Camera Thermal Drift Costs a Dimensional Measurement ―― A 40 mm square measured by a camera whose focal length, mount and principal point drift with temperature ΔT, the error split into a + b·R in image radius. At ΔT = 15 K the constant term is +224.5 ppm (the single-cause control gives +225.3 ppm). Drift rises above the noise floor (23 ppm for a 25-frame average) only from ΔT = 1.6 K; below that, the honest report is that no temperature effect is visible.
↑ The measurement ―― 焦点距離ドリフトは R に依らない。主点ドリフトは R に比例(歪みを外す中心がずれるため)。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 周辺のワークのほうが感度が高い。
py -3.11 examples/poc_thermal_drift_metrology.py
Source: examples/poc_thermal_drift_metrology.py
This run produced 4 figures in total - see them all
Ops used (notes): lockin_thermal_wave · polygon_area · thermo_optic_coefficient
A PSF (point spread function) whose core stays sharp while only its tail is made heavier, with the slanted-edge MTF (modulation transfer function) and the black level of a dark square measured on the same image. Raising the tail fraction from 0 to 0.20 moves MTF50 from 0.2347 to 0.2249 cyc/px (-4.2 %, still passing) while the black level goes from 0.0 to 15.7 % (failing). A ±16 px measurement window captures only 6 % of the tail’s energy; a glare figure means nothing until the measurement extent is declared.
↑ Veiling Glare Breaks Contrast Measurement — MTF (modulation transfer function) Passes While Black Level Fails ―― A PSF (point spread function) whose core stays sharp while only its tail is made heavier, with the slanted-edge MTF (modulation transfer function) and the black level of a dark square measured on the same image. Raising the tail fraction from 0 to 0.20 moves MTF50 from 0.2347 to 0.2249 cyc/px (-4.2 %, still passing) while the black level goes from 0.0 to 15.7 % (failing). A ±16 px measurement window captures only 6 % of the tail’s energy; a glare figure means nothing until the measurement extent is declared.
Terms: MTF — The magnitude of the optical transfer function, showing how image contrast is transferred at each spatial frequency.; PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.; MTF50 — The spatial frequency at which MTF falls to 50% of its low-frequency value, used as a single-number resolution summary.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ D を 16 倍にすると 19.6 % が 4.5 % になる。
↑ 全 PSF の MTF と正弦チャートの絶対コントラストは 0.80 の台地を見る。
py -3.11 examples/poc_veiling_glare.py
Source: examples/poc_veiling_glare.py
This run produced 4 figures in total - see them all
Ops used (notes): airy_pattern · create_funct_1d_pairs · derivate_funct_1d · edge_spread · get_y_value_funct_1d · invert_funct_1d · mtf50 · mtf_diffraction · psf_to_mtf · sfr_from_edge · veiling_glare_index
One synthetic sensor with planted values (eta 0.45, K 0.25 DN/e-, dark noise 6 e-, 12 bit, 640 x 512, column and pixel DSNU (dark signal non-uniformity) and PRNU (photo-response non-uniformity), 30 hot pixels) is put through one in-camera processing step at a time and re-measured with the EMVA 1288 (image-sensor characterization standard) Release 4.0 Linear procedure, against the planted truth. The quantum efficiency moves by 0 % under a x2 digital gain (K +100 %, saturation capacity -51 %), by +83 % under a denoiser that mixes in 30 % of a 3 x 3 mean (0.825, still below 1 and plausible; saturation capacity +89 %, maximum SNR +2.8 dB), by -53 % under sharpening, by +87 % under recursive temporal denoising (alpha 0.8), by 0 % under factory-map defect correction (DSNU -10 %, defects 29 -> 0) and by +156 % under log companding (eta > 1, so the sensitivity-threshold operator refuses). Linear processing is predictable in closed form: with S = sum(w^2)/sum(w)^2 * alpha^2/(2 - alpha), K’ = sum(w) S K and eta’ = eta/S; prediction and measurement agree within 3 % for K, eta and sigma_d (an exact form that filters the raw quantisation noise too) and within 4 % for DSNU and PRNU with per-component gains. The standard’s equation 51 linearity check catches only the companding (171 %); denoising, sharpening and temporal filtering keep the photon transfer straight and only tilt it (0.4-1.2 %). A direct test of assumption 3 (temporal noise independent between pixels and between images; this library’s check, not a number of the standard) catches those three through the neighbour correlation of a frame difference (+0.098 / -0.072, as predicted) and the correlation with the frame after next (0.199 = 1 - alpha). Defect correction escapes every check. Release 4.0 General recovers the input SNR of the sensor behind the compander within 5 % its PRNU within 5 % and a relative quantum efficiency scan within 2 % (Linear PRNU: -39 %; equation 74 as printed is off by 589 % when the photon counts differ between wavelengths, the ratio should divide), but keeps the denoising lie: the input SNR stays 1/sqrt(S) = 1.35 times the truth, and the General SNRmax of the denoised camera reads 217, 1.71 times the truth at saturation, because saturation is set on the processed output while raw pixels clip earlier. The section 8.1 highpass drops a 9-pixel border, so one hot pixel there is invisible to section 8.8. Synthetic sensor, simplified processing, uniform illumination.
↑ In-Camera Processing Makes Datasheet Numbers Lie Plausibly - Denoising Adds 83 % to the Quantum Efficiency, the Standard’s Check Misses It, Noise Correlation Catches It ―― One synthetic sensor with planted values (eta 0.45, K 0.25 DN/e-, dark noise 6 e-, 12 bit, 640 x 512, column and pixel DSNU (dark signal non-uniformity) and PRNU (photo-response non-uniformity), 30 hot pixels) is put through one in-camera processing step at a time and re-measured with the EMVA 1288 (image-sensor characterization standard) Release 4.0 Linear procedure, against the planted truth. The quantum efficiency moves by 0 % under a x2 digital gain (K +100 %, saturation capacity -51 %), by +83 % under a denoiser that mixes in 30 % of a 3 x 3 mean (0.825, still below 1 and plausible; saturation capacity +89 %, maximum SNR +2.8 dB), by -53 % under sharpening, by +87 % under recursive temporal denoising (alpha 0.8), by 0 % under factory-map defect correction (DSNU -10 %, defects 29 -> 0) and by +156 % under log companding (eta > 1, so the sensitivity-threshold operator refuses). Linear processing is predictable in closed form: with S = sum(w^2)/sum(w)^2 * alpha^2/(2 - alpha), K’ = sum(w) S K and eta’ = eta/S; prediction and measurement agree within 3 % for K, eta and sigma_d (an exact form that filters the raw quantisation noise too) and within 4 % for DSNU and PRNU with per-component gains. The standard’s equation 51 linearity check catches only the companding (171 %); denoising, sharpening and temporal filtering keep the photon transfer straight and only tilt it (0.4-1.2 %). A direct test of assumption 3 (temporal noise independent between pixels and between images; this library’s check, not a number of the standard) catches those three through the neighbour correlation of a frame difference (+0.098 / -0.072, as predicted) and the correlation with the frame after next (0.199 = 1 - alpha). Defect correction escapes every check. Release 4.0 General recovers the input SNR of the sensor behind the compander within 5 % its PRNU within 5 % and a relative quantum efficiency scan within 2 % (Linear PRNU: -39 %; equation 74 as printed is off by 589 % when the photon counts differ between wavelengths, the ratio should divide), but keeps the denoising lie: the input SNR stays 1/sqrt(S) = 1.35 times the truth, and the General SNRmax of the denoised camera reads 217, 1.71 times the truth at saturation, because saturation is set on the processed output while raw pixels clip earlier. The section 8.1 highpass drops a 9-pixel border, so one hot pixel there is invisible to section 8.8. Synthetic sensor, simplified processing, uniform illumination.
Terms: DSNU — Spatial pixel-to-pixel variation of the mean dark signal when an image sensor receives no light.; PRNU — Spatial pixel-to-pixel variation in an image sensor’s response under uniform illumination.; EMVA 1288 — A standard measurement procedure and set of quantities for sensitivity, noise, saturation, and non-uniformity of machine-vision image sensors.
↑ The measurement ―― 拒否 = η > 1 で絶対感度しきい値(式 26)の op が拒んだ。DSNU・PRNU は照明が一様なので高域フィルタなし。 (figure labels are in Japanese; the numbers are the same)
↑ 前処理なしの差は白い砂嵐。
↑ 傾きがシステムゲイン K。
↑ General は出力の雑音を特性曲線の傾きで入力へ戻す(式 9・10)。
py -3.11 examples/poc_emva1288_in_camera_processing.py
Source: examples/poc_emva1288_in_camera_processing.py
This run produced 5 figures in total - see them all
Ops used (notes): emva_bspline_regression · emva_defect_pixels · emva_dynamic_range · emva_filter_distortion · emva_general_nonuniformity · emva_general_relative_qe · emva_general_sensitivity · emva_highpass · emva_linearity_error · emva_noise_whiteness · emva_pair_statistics · emva_photon_transfer · emva_ptc_linearity_check · emva_quantum_efficiency · emva_sensitivity_threshold · emva_snr_curve · emva_spatial_nonuniformity
In roll-to-roll production of film, battery electrode, copper foil and paper, a single scar on a conveying roller prints itself onto the web once per roller circumference. Can the machine-direction spectrum of a defect map recover that circumference and name the culprit against a table of pi*D? Reading the naive spectral maximum names a real but innocent roller: an impulse train has a comb whose harmonics are as tall as the fundamental, so the maximum grabs C/2 = 235.62 mm, and that lands on the cooling roller at 314.16 mm in the table. A circumference that exists nowhere would be caught; one that exists elsewhere in the table passes review. That error stays at 235.9 mm as the miss rate goes from 0 to 50 % — a constant error is not evidence of robustness, it is the same mistake repeated. The fix is fail-closed: take the lowest frequency at which harmonics k=1..3 all stand. The null baseline, the median spacing between detected defects, is right when detection is perfect (-0.03 mm) but at a 40 % miss rate it errs by 472.1 mm and the mean by 813.5 mm, while the comb method holds at 3.2 mm — a missed defect removes amplitude without moving phase. A 25 mm meander, uncorrected, splits one roller’s defect line into two across the web (periodic defects remaining in the lane fall from 100 % to 44 %), yet the MD spectrum is bit-for-bit unchanged: meander only breaks methods that cut lanes in the cross direction. Sixty control trials with only healthy rollers give a 1.7 % false-positive rate, and the floor is not zero — the healthy side reaches 3.99 times the band median, above the 2.5 threshold on its own, so what actually holds the line is the demand that every harmonic stand. There are two cliffs. The prediction L_crit = C^2/dC = 14137 mm matches the measured resolution cliff at 14000 mm, a ratio of 0.99. But the detection cliff, the shortest record that still always reports, is 17000 mm — longer. Shortening the record makes you say nothing before it makes you say the wrong roller, so the cliff Rayleigh predicted correctly is never reached. The criterion itself needed repair: at 24 trials a clean 0 % existed, at 120 trials 2 % survives even at the longest record, so 0 % was a small-sample artefact rather than a floor. The most frequent wrong answer, at seven of nine sweep points, is the cooling roller, and 471.24 / 314.16 = 1.500 is exactly 3:2: an integer ratio inside the table gives the comb method a misidentification of its own. The two gaps this PoC found have since been filled: fs.point_spectrum takes a periodogram straight from event positions and carries its own resolution in the return value, and fs.peak_subbin refines a peak below the bin without clamping the shift, since a vertex further than half a sample away means the index was not a maximum at all.
↑ Naming the Damaged Roller from a Period — You Run Out of Evidence Before You Reach the Cliff ―― In roll-to-roll production of film, battery electrode, copper foil and paper, a single scar on a conveying roller prints itself onto the web once per roller circumference. Can the machine-direction spectrum of a defect map recover that circumference and name the culprit against a table of piD? Reading the naive spectral maximum names a real but innocent roller: an impulse train has a comb whose harmonics are as tall as the fundamental, so the maximum grabs C/2 = 235.62 mm, and that lands on the cooling roller at 314.16 mm in the table. A circumference that exists nowhere would be caught; one that exists elsewhere in the table passes review. That error stays at 235.9 mm as the miss rate goes from 0 to 50 % — a constant error is not evidence of robustness, it is the same mistake repeated. The fix is fail-closed: take the lowest frequency at which harmonics k=1..3 all stand. The null baseline, the median spacing between detected defects, is right when detection is perfect (-0.03 mm) but at a 40 % miss rate it errs by 472.1 mm and the mean by 813.5 mm, while the comb method holds at 3.2 mm — a missed defect removes amplitude without moving phase. A 25 mm meander, uncorrected, splits one roller’s defect line into two across the web (periodic defects remaining in the lane fall from 100 % to 44 %), yet the MD spectrum is bit-for-bit unchanged: meander only breaks methods that cut lanes in the cross direction. Sixty control trials with only healthy rollers give a 1.7 % false-positive rate, and the floor is not zero — the healthy side reaches 3.99 times the band median, above the 2.5 threshold on its own, so what actually holds the line is the demand that every harmonic stand. There are two cliffs. The prediction L_crit = C^2/dC = 14137 mm matches the measured resolution cliff at 14000 mm, a ratio of 0.99. But the detection cliff, the shortest record that still always reports, is 17000 mm — longer. Shortening the record makes you say nothing before it makes you say the wrong roller, so the cliff Rayleigh predicted correctly is never reached. The criterion itself needed repair: at 24 trials a clean 0 % existed, at 120 trials 2 % survives even at the longest record, so 0 % was a small-sample artefact rather than a floor. The most frequent wrong answer, at seven of nine sweep points, is the cooling roller, and 471.24 / 314.16 = 1.500 is exactly 3:2: an integer ratio inside the table gives the comb method a misidentification of its own. The two gaps this PoC found have since been filled: fs.point_spectrum takes a periodogram straight from event positions and carries its own resolution in the return value, and fs.peak_subbin refines a peak below the bin without clamping the shift, since a vertex further than half a sample away means the index was not a maximum at all.*
↑ The measurement ―― 見逃しは位相を飛ばさないので、櫛の山は低くなるだけで動かない。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ 縦線が閉形式の予測 C²/ΔC。
↑ この回の図
py -3.11 examples/poc_web_roll_periodicity.py
Source: examples/poc_web_roll_periodicity.py
This run produced 10 figures in total - see them all
Ops used (notes): cepstrum · find_peaks · local_min_max_funct_1d · median · peak_subbin · point_spectrum · smooth_funct_1d_gauss · spectrum
The profile h(x) recovered from the row position of a single laser line, with reinforcement height, width and undercut read off it. The centroid beats the null (integer argmax per column) by 9.3x, and with zero noise the log-parabola is exact to machine precision, 12 orders better than a plain parabola; at 1 % noise the two are 0.00272 versus 0.00260 mm, indistinguishable. Five spatter points break all three estimators together to 0.11 mm (40x); what matters is not refinement but which peak you pick.
↑ Weld Bead From a Laser-Triangulation Profile — The Sin of Writing 0 Where Nothing Was Measured ―― The profile h(x) recovered from the row position of a single laser line, with reinforcement height, width and undercut read off it. The centroid beats the null (integer argmax per column) by 9.3x, and with zero noise the log-parabola is exact to machine precision, 12 orders better than a plain parabola; at 1 % noise the two are 0.00272 versus 0.00260 mm, indistinguishable. Five spatter points break all three estimators together to 0.11 mm (40x); what matters is not refinement but which peak you pick.
↑ The measurement ―― 0 で埋めた線は影の区間で h=0 に張り付き、左のアンダーカットが消えて偽のつま先ができる。 (figure labels are in Japanese; the numbers are the same)
↑ 真値 0.45 mm。
↑ 遮蔽が無ければ全部 1 % 以内。
py -3.11 examples/poc_weld_bead_profile.py
Source: examples/poc_weld_bead_profile.py
This run produced 4 figures in total - see them all
Ops used (notes): gen_measure_rectangle2 · measure_pos · median · smooth_funct_1d_mean
A fillet-weld cross-section is built in closed form with convexity, leg lengths, throat and undercut depth varied along the weld line by known functions, then the triangulation angle is swept from 15 to 70 degrees. Height resolution improves as 1/sin(theta) while any face rising steeper than cot(theta) hides in its own shadow, so the far parent plate turns away exactly at the predicted 37.0 degrees and the far undercut starts vanishing earlier, at 20.8-34.5 degrees (the deeper the groove, the sooner). The dangerous parts are that at 28 degrees every cross-section still reports a value while 25 % of the groove span is unmeasured and the depth reads 27 % shallow, and that raising the angle further drops the deepest cross-sections out of the average (true mean drifting 0.260 to 0.047 mm) — a survivorship bias; the optimal angle differs per measurand at 20 / 24 / 36 / 64 degrees.
↑ Light-section scanning of a weld bead — resolution and occlusion share one knob ―― A fillet-weld cross-section is built in closed form with convexity, leg lengths, throat and undercut depth varied along the weld line by known functions, then the triangulation angle is swept from 15 to 70 degrees. Height resolution improves as 1/sin(theta) while any face rising steeper than cot(theta) hides in its own shadow, so the far parent plate turns away exactly at the predicted 37.0 degrees and the far undercut starts vanishing earlier, at 20.8-34.5 degrees (the deeper the groove, the sooner). The dangerous parts are that at 28 degrees every cross-section still reports a value while 25 % of the groove span is unmeasured and the depth reads 27 % shallow, and that raising the angle further drops the deepest cross-sections out of the average (true mean drifting 0.260 to 0.047 mm) — a survivorship bias; the optimal angle differs per measurand at 20 / 24 / 36 / 64 degrees.
↑ The measurement ―― θ を大きくすると高さの伸び K が増えて分解能は上がる(輝線の起伏が大きくなる)が、左半分の輝線が消えていく。 (figure labels are in Japanese; the numbers are the same)
↑ θ = 28 度。
↑ 下に凸の谷がアンダーカット。
↑ 同じノブの表裏。
↑ 脚長は sin²(母材角)、溝深さは cos²(母材角) —— 同じ母材面で足すと 1。
py -3.11 examples/poc_weld_bead_scan_angle.py
Source: examples/poc_weld_bead_scan_angle.py
This run produced 11 figures in total - see them all
Ops used (notes): esdf · fit_plane_3d · intersect_planes · lines_gauss · normals_from_depth · occupancy_grid · query_distance
Because the intended string is given as input, no 6000-way classification is needed: each cell is compared against one specified character. The threshold is not guessed but derived from the 95th percentile of the distance between the same character in different typefaces (0.058). The distance is the 99th percentile rather than the mean — a substitution usually keeps one radical and swaps the rest, so the mean sinks the 検/横 pair to 0.0171, below the typeface floor, making it undetectable in principle. On a synthetic sign all four planted errors are found with no false alarms, and after replacement the distance drops from 0.0805 to 0.0325, below the floor for all four.
↑ Finding and Fixing Wrong Characters Without Recognising Them — the Threshold Comes from Typeface Spread ―― Because the intended string is given as input, no 6000-way classification is needed: each cell is compared against one specified character. The threshold is not guessed but derived from the 95th percentile of the distance between the same character in different typefaces (0.058). The distance is the 99th percentile rather than the mean — a substitution usually keeps one radical and swaps the rest, so the mean sinks the 検/横 pair to 0.0171, below the typeface floor, making it undetectable in principle. On a synthetic sign all four planted errors are found with no false alarms, and after replacement the distance drops from 0.0805 to 0.0325, below the floor for all four.
↑ The measurement ―― 床を超えたマスが誤字。壊した位置は 2, 3, 5, 7。 (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_glyph_typo_detection.py
Source: examples/poc_glyph_typo_detection.py
This run produced 2 figures in total - see them all
3D-printer data flows shape (mesh) → layers (slices) → path (G-code) → the layer images taken during printing. The new family printpath (11 ops, numpy + stdlib, no new types) closes that loop with the existing vocabulary (mesh / voxel / table / image2d), so the truth can be planted by hand. A box with a square hole and a gear-like boss with a round hole sliced at 0.2 mm give layer-mask areas matching the closed form (184.00 mm² exactly and within 0.07 %), a layer where only the hole remains is empty (contours oriented by the triangle normals, nonzero winding), the extrusion of a path that walks the contours equals perimeter × width × height / filament area exactly, G-code round-trips through write and read (relative coordinates, relative E, retracts, G92 and inches are honoured; arcs and missing coordinates are refused), and 3MF round-trips too. Inspection: six gaps and six blobs (1–4 mm) planted into the expected layer raster (gcode_layer_image) are caught by print_layer_defect_map with sign, recall 0.959 and zero false positives at a 3 px (0.3 mm) tolerance; a 0.2 mm camera shift drops recall to 0.895 and precision to 0.946 because the tolerance eats the defect edges — the curve over tolerance is shown as it is.
↑ Print Layer Inspection — Closing the Shape → Layer → Path → Image Loop and Catching Planted Defects by the Numbers ―― 3D-printer data flows shape (mesh) → layers (slices) → path (G-code) → the layer images taken during printing. The new family printpath (11 ops, numpy + stdlib, no new types) closes that loop with the existing vocabulary (mesh / voxel / table / image2d), so the truth can be planted by hand. A box with a square hole and a gear-like boss with a round hole sliced at 0.2 mm give layer-mask areas matching the closed form (184.00 mm² exactly and within 0.07 %), a layer where only the hole remains is empty (contours oriented by the triangle normals, nonzero winding), the extrusion of a path that walks the contours equals perimeter × width × height / filament area exactly, G-code round-trips through write and read (relative coordinates, relative E, retracts, G92 and inches are honoured; arcs and missing coordinates are refused), and 3MF round-trips too. Inspection: six gaps and six blobs (1–4 mm) planted into the expected layer raster (gcode_layer_image) are caught by print_layer_defect_map with sign, recall 0.959 and zero false positives at a 3 px (0.3 mm) tolerance; a 0.2 mm camera shift drops recall to 0.895 and precision to 0.946 because the tolerance eats the defect edges — the curve over tolerance is shown as it is.
↑ The measurement ―― gear-like boss with a round hole sliced at 0.2 mm into 30 layer masks (mesh_slice_stack) and rendered as a solid, grey = height (figure labels are in Japanese; the numbers are the same)
↑ precision and recall of the defect map against the injected truth as the tolerance grows, without and with a 0.2 mm camera shift: a small tolerance ab…
↑ every number with the bar it had to clear
py -3.11 examples/poc_print_layer_inspection.py
Source: examples/poc_print_layer_inspection.py
This run produced 4 figures in total - see them all
Ops used (notes): contours_to_gcode · gcode_extrusion_volume · gcode_layer_image · gcode_read · gcode_time_estimate · gcode_write · mesh_slice_contours · mesh_slice_stack · print_layer_defect_map · prism_mesh · read_3mf · vol_render_transfer · write_3mf
The author’s remark: “we have done nothing on AGVs, the simple vehicles manufacturing uses everywhere”. A transport plan for 12 vehicles on a floor grid is built with focal-search CBS (cost 96 ≤ 1.1 × lower bound 88), then executed 1000 times with each vehicle delayed with probability 0.3 per step. Naive execution (“move if the next cell is free”) jams in 641 of 1000 runs; execution that follows the action dependency graph (ADG, Hönig et al. 2019) has 0 collisions and 0 deadlocks. Of the 641 jams, 640 were a vehicle that had arrived and stayed parked in the aisle; only 1 was a cycle of waits. Gates: the CBS sum of costs matches the optimum of A* over the joint state of all vehicles (a second implementation) in 40 / 40 cases, focal search ≤ 1.3 × optimum in 40 / 40, a concrete instance that prioritized planning cannot solve in any order (CBS and joint A* both cost 9), and every VDA 5050 order passes the structural rules. Honestly: one step is a fixed time on a discrete grid; acceleration, turning and vehicle size are not modelled.
↑ A Warehouse That Does Not Jam When Vehicles Run Late — A Correct Plan Alone Still Stops an AGV Fleet ―― The author’s remark: “we have done nothing on AGVs, the simple vehicles manufacturing uses everywhere”. A transport plan for 12 vehicles on a floor grid is built with focal-search CBS (cost 96 ≤ 1.1 × lower bound 88), then executed 1000 times with each vehicle delayed with probability 0.3 per step. Naive execution (“move if the next cell is free”) jams in 641 of 1000 runs; execution that follows the action dependency graph (ADG, Hönig et al. 2019) has 0 collisions and 0 deadlocks. Of the 641 jams, 640 were a vehicle that had arrived and stayed parked in the aisle; only 1 was a cycle of waits. Gates: the CBS sum of costs matches the optimum of A over the joint state of all vehicles (a second implementation) in 40 / 40 cases, focal search ≤ 1.3 × optimum in 40 / 40, a concrete instance that prioritized planning cannot solve in any order (CBS and joint A* both cost 9), and every VDA 5050 order passes the structural rules. Honestly: one step is a fixed time on a discrete grid; acceleration, turning and vehicle size are not modelled.*
↑ The measurement ―― 最後のコマ(左: デッドロック、右: 全台到着) (figure labels are in Japanese; the numbers are the same)
↑ 同じ 12 台の計画を、遅れの確率ごとに 200 通り走らせたデッドロックの割合。
↑ The animation ―― 優先度付き計画が、どちらを先にしても解けない問題(CBS の総コスト 9)。CBS は片方に道を譲らせて解く
py -3.11 examples/poc_agv_fleet.py
Source: examples/poc_agv_fleet.py
This run produced 4 figures in total - see them all
Ops used (notes): adg_build · adg_execute · arrow · mapf_cbs · mapf_ecbs · mapf_joint_astar · mapf_prioritized · naive_execute · plan_conflicts · text_box · vda5050_check · vda5050_order · warehouse_grid
A thinned 100 um silicon die over a hybrid bond (SiO2 1 + 1 um, Cu pads 1 + 1 um of 40 um diameter at 80 um pitch) with voids of 5 to 24 um placed on pads and in the dielectric, imaged by a focused scanning acoustic microscope (1 mm of water, F/3); echoes come from a layer transfer matrix checked against a bottom-up Airy recursion (Airy multiple-reflection formula), a time-domain ray sum and the optical thin-film formula to 1e-12. Read at the expected time of the bond plane, the dielectric echo is at -91 deg and the Cu pad at +84 deg (thin layers give quadrature echoes) while voids are inverted (-175 deg in the dielectric, +159 deg on a pad). In the amplitude image 8 of 12 voids hide inside the brightness range of intact pixels: an inverted echo adds at right angles to a quadrature background, so voids of 9 to 16 um change the amplitude by 0.4 to 3.5 sigma but the phase-aware statistic by 3.0 to 8.9 sigma; the phase image flags every void of 16 um and up and 1 of 26,724 intact pixels. The conventional single-sample polarity of an intact Cu pad flips 5 times between 140 and 400 MHz. The optimal frequency has a closed form (139.16 MHz for a 10 um void, equal to brute force) and the front-echo tail sets a floor of 130 MHz. POD (a-hat vs a, 88 voids): a90/95 of the phase-aware detector is 12.4 / 13.9 / 57.6 um at 150 / 200 / 300 MHz against 16.6 / 19.3 / 101.6 um for amplitude only, under the same baseline and threshold rule. Synthetic: 1-D plane-wave longitudinal layers, a Gaussian lateral PSF, no refraction aberration, silicon attenuation set to 0 (unverified), no kissing bonds.
↑ Find Hybrid-Bond Voids by Echo Phase, Not Brightness - Small Voids Hide in the Amplitude Image and Light Up Inverted in the Phase Image ―― A thinned 100 um silicon die over a hybrid bond (SiO2 1 + 1 um, Cu pads 1 + 1 um of 40 um diameter at 80 um pitch) with voids of 5 to 24 um placed on pads and in the dielectric, imaged by a focused scanning acoustic microscope (1 mm of water, F/3); echoes come from a layer transfer matrix checked against a bottom-up Airy recursion (Airy multiple-reflection formula), a time-domain ray sum and the optical thin-film formula to 1e-12. Read at the expected time of the bond plane, the dielectric echo is at -91 deg and the Cu pad at +84 deg (thin layers give quadrature echoes) while voids are inverted (-175 deg in the dielectric, +159 deg on a pad). In the amplitude image 8 of 12 voids hide inside the brightness range of intact pixels: an inverted echo adds at right angles to a quadrature background, so voids of 9 to 16 um change the amplitude by 0.4 to 3.5 sigma but the phase-aware statistic by 3.0 to 8.9 sigma; the phase image flags every void of 16 um and up and 1 of 26,724 intact pixels. The conventional single-sample polarity of an intact Cu pad flips 5 times between 140 and 400 MHz. The optimal frequency has a closed form (139.16 MHz for a 10 um void, equal to brute force) and the front-echo tail sets a floor of 130 MHz. POD (a-hat vs a, 88 voids): a90/95 of the phase-aware detector is 12.4 / 13.9 / 57.6 um at 150 / 200 / 300 MHz against 16.6 / 19.3 / 101.6 um for amplitude only, under the same baseline and threshold rule. Synthetic: 1-D plane-wave longitudinal layers, a Gaussian lateral PSF, no refraction aberration, silicon attenuation set to 0 (unverified), no kissing bonds.
Terms: SAM — An instrument that scans focused ultrasound over a specimen and images internal interfaces or defects from echo amplitude, phase, or arrival time.; Airy — The formula for a parallel plate’s transmittance obtained by summing its infinite series of multiple reflections.
↑ The measurement ―― 接合面のエコーは表面エコーの 1 割未満。ボイドのエコーは反転、誘電体とパッドは直交(微分形)。 (figure labels are in Japanese; the numbers are the same)
↑ 期待時刻(24.1 ns)で読んだ位相は、誘電体 -91°、Cu パッド +84°、ボイド(誘電体) -175°、ボイド(パッドの上) +159°。
↑ S(f) = (1 − 2^−(a/w)²) exp(−β f²)。
↑ FWHM = 1.02 λ_w F#(F# 3)、水の経路 1 mm(2.17e-3 dB/(cm MHz²))。
↑ 閾値は同じ規則(良品の部位の平均 + 3.09σ、パッドと誘電体の厳しい方)。
↑ The animation ―― 150 → 350 MHz。左 = 振幅(各コマで正規化)、右 = cos(位相)。周波数を上げると像は締まるが、水の減衰で雑音に沈んでいく。
py -3.11 examples/poc_sam_hybrid_bonding_voids.py
Source: examples/poc_sam_hybrid_bonding_voids.py
This run produced 8 figures in total - see them all
Ops used (notes): acoustic_interface_coefficients · acoustic_material_table · echo_phase_polarity · pod_ahat_fit · pod_false_call · pod_from_ahat · pulse_echo_ascan · sam_cscan · sam_frequency_tradeoff
A photoluminescence (PL) image glows only after the excess carriers have diffused, so a second PSF (point spread function) set by the diffusion length L = sqrt(D tau) (the 2-D Green function K0(r/L)/2 pi L^2) sits on top of the optical one. Three new ops: pl_diffusion_psf (the kernel, sum 1), pl_image_simulate (an exact grid solve of the steady-state diffusion equation from an excitation map and a recombination map) and pl_groove_visibility (closed-form contrast, CNR and visibility boundary L* from groove width, diffusion length and noise). The pre-run prediction that the same groove fades on a better wafer with a longer diffusion length came out half right: a shadow groove (missing excitation, 1 - exp(-w/2L)) fades from 11.8 to 1.6 % as L goes 20 to 160 um, but a sink groove (a recombination line, Donolato’s SL/(SL+2D)) deepens from 48.9 to 89.0 % (grid closed form to 1e-10, continuous form within 2.4 %, Donolato within 1.7 %). Public measured data (laser grooves in Si wafers after HF treatment at 0 min / 55 min / 1 week, CC BY 4.0, DOI 10.5281/zenodo.19603335) are read from the raw images, the paper’s histogram (L1 0) and line scans (RMS within 1.1 %) are reproduced first, and then the groove is deeper (50 to 35 %) and wider (FWHM 1.32 to 0.42 mm) right after HF: the sink direction. The PL level is no proxy for the diffusion length (55 min has the lowest level but intermediate depth and width). In an embedded-groove POD the longer-L 0 min ground makes the shadow a90 4.6 times larger (hidden) and the sink a90 0.28 times (revealed); the measured a90 is more than an order of magnitude looser than the white-noise closed form, so the limit is set by the structure of the ground, not by noise. The real-data stage runs only when the environment variable is set; no data is bundled.
↑ Why a Thin Groove Vanishes in a PL Image — a Longer Diffusion Length Hides Only Shadow Grooves, and Makes Sink Grooves Stand Out ―― A photoluminescence (PL) image glows only after the excess carriers have diffused, so a second PSF (point spread function) set by the diffusion length L = sqrt(D tau) (the 2-D Green function K0(r/L)/2 pi L^2) sits on top of the optical one. Three new ops: pl_diffusion_psf (the kernel, sum 1), pl_image_simulate (an exact grid solve of the steady-state diffusion equation from an excitation map and a recombination map) and pl_groove_visibility (closed-form contrast, CNR and visibility boundary L from groove width, diffusion length and noise). The pre-run prediction that the same groove fades on a better wafer with a longer diffusion length came out half right: a shadow groove (missing excitation, 1 - exp(-w/2L)) fades from 11.8 to 1.6 % as L goes 20 to 160 um, but a sink groove (a recombination line, Donolato’s SL/(SL+2D)) deepens from 48.9 to 89.0 % (grid closed form to 1e-10, continuous form within 2.4 %, Donolato within 1.7 %). Public measured data (laser grooves in Si wafers after HF treatment at 0 min / 55 min / 1 week, CC BY 4.0, DOI 10.5281/zenodo.19603335) are read from the raw images, the paper’s histogram (L1 0) and line scans (RMS within 1.1 %) are reproduced first, and then the groove is deeper (50 to 35 %) and wider (FWHM 1.32 to 0.42 mm) right after HF: the sink direction. The PL level is no proxy for the diffusion length (55 min has the lowest level but intermediate depth and width). In an embedded-groove POD the longer-L 0 min ground makes the shadow a90 4.6 times larger (hidden) and the sink a90 0.28 times (revealed); the measured a90 is more than an order of magnitude looser than the white-noise closed form, so the limit is set by the structure of the ground, not by noise. The real-data stage runs only when the environment variable is set; no data is bundled.*
Terms: PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.
↑ The measurement ―― 同じ幅 3 µm の溝を拡散長 L = 4 / 16 / 64 µm で描いた PL 像(雑音 1 %)。上段 = 影の溝(励起の欠け): L が長いほど拡散が埋めて消える。下段 = 沈みの溝(再結合の線): L が長いほど深く・広く見える。良質なウェハで隠れるのは影だけ。 (figure labels are in Japanese; the numbers are the same)
↑ 幅 5 µm の溝の中心コントラスト対拡散長(閉形式)。
py -3.11 examples/poc_si_wafer_pl_defect_visibility.py
Source: examples/poc_si_wafer_pl_defect_visibility.py
This run produced 3 figures in total - see them all
Ops used (notes): pl_diffusion_psf · pl_groove_visibility · pl_image_simulate · pod_hitmiss_fit
| For shallow dents, scratches on a hairline finish and satin unevenness on near-specular metal parts, what shows is set less by the type of light than by the half-angle α and axis of the cone of light reaching each point, relative to the cone the camera accepts (object-side NA, half-angle β): the illumination-NA / objective-NA relation of Koehler illumination and the knife edge of schlieren optics. Reflection is an isometry of the sphere of directions, so the brightness is the intersection of two spherical caps, a closed form: slopes below | α − β | /2 do not show, slopes beyond (α + β)/2 go dark. Five new ops in illumdesign (solid_angle_light, solid_angle_boundaries, solid_angle_brightness, solid_angle_sweep, solid_angle_render). The closed form agrees with the projected-solid-angle pupil integral to 0.002. Through a telecentric lens of NA 0.033 a 2 µm dent (steepest slope 0.25°) shows as a ring only with α = β (contrast −0.091 measured on the image, closed form to 1e-4) and vanishes at α = 0.5° and 7°, as the closed form predicts. The closed form picks α between 3.5° and 6.1° to hide hairline marks (99th-percentile slope 0.82°) while blackening a 4° scratch: at α = 4° the marks fluctuate 0.014 times as much as at α = β and the scratch contrast is −1.00. Tilting the cone by β puts the flat ground at 0.39 and splits the dent’s flanks into 0.53 and 0.25 (closed form 0.54 / 0.25), so the slope sign reads as bright or dark. Satin patches (micro-facet spread 1.3° against 0.8°) give −0.20 at α = β and −0.02 at α = 7°. Where it breaks: ±0.25° waviness makes the background swing 1.6 times the dent’s dip at α = β, so a threshold cannot pick the dent; a micro-facet spread of 0.25° already cuts the contrast to a third, and at 4° no cone gets above 0.001. No comparison photographs taken with only the illumination solid angle changed were found under a reusable licence, so the check against real images is still open. Nothing learned, nothing calibrated, 15 gates. |
| *↑ Illumination Cone versus Imaging NA — Change Only the Opening of the Light Cone and the Same Dent, Scratch and Satin Patch Vanish or Appear ―― For shallow dents, scratches on a hairline finish and satin unevenness on near-specular metal parts, what shows is set less by the type of light than by the half-angle α and axis of the cone of light reaching each point, relative to the cone the camera accepts (object-side NA, half-angle β): the illumination-NA / objective-NA relation of Koehler illumination and the knife edge of schlieren optics. Reflection is an isometry of the sphere of directions, so the brightness is the intersection of two spherical caps, a closed form: slopes below | α − β | /2 do not show, slopes beyond (α + β)/2 go dark. Five new ops in illumdesign (solid_angle_light, solid_angle_boundaries, solid_angle_brightness, solid_angle_sweep, solid_angle_render). The closed form agrees with the projected-solid-angle pupil integral to 0.002. Through a telecentric lens of NA 0.033 a 2 µm dent (steepest slope 0.25°) shows as a ring only with α = β (contrast −0.091 measured on the image, closed form to 1e-4) and vanishes at α = 0.5° and 7°, as the closed form predicts. The closed form picks α between 3.5° and 6.1° to hide hairline marks (99th-percentile slope 0.82°) while blackening a 4° scratch: at α = 4° the marks fluctuate 0.014 times as much as at α = β and the scratch contrast is −1.00. Tilting the cone by β puts the flat ground at 0.39 and splits the dent’s flanks into 0.53 and 0.25 (closed form 0.54 / 0.25), so the slope sign reads as bright or dark. Satin patches (micro-facet spread 1.3° against 0.8°) give −0.20 at α = β and −0.02 at α = 7°. Where it breaks: ±0.25° waviness makes the background swing 1.6 times the dent’s dip at α = β, so a threshold cannot pick the dent; a micro-facet spread of 0.25° already cuts the contrast to a third, and at 4° no cone gets above 0.001. No comparison photographs taken with only the illumination solid angle changed were found under a reusable licence, so the check against real images is still open. Nothing learned, nothing calibrated, 15 gates.* |
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_illumination_cone_vs_na/01_brightness_vs_slope.png) |
| *↑ The measurement ―― 鏡面の傾き θ と、カメラの瞳が反射した光の円錐で覆われる割合(閉形式)。α = 7° と 0.5° は傾き | α − β | /2 まで平らで、そこまでの傾きは見えない。α = β は 0 から下がり始めるので最も小さな傾きが見える。β だけ傾けた円錐は平らな地が半分の明るさで、傾きの向きで明るくも暗くもなる。 (figure labels are in Japanese; the numbers are the same)* |
↑ 傾き θ の面と平らな地のコントラストを α に対して(微小面の粗さ 0)。
↑ 面のうねり(周期 1.5 mm、傾き ±0.25°)を足した鏡面の打痕。
↑ α を 0.5〜3° から選んだ最大のコントラスト。
↑ The animation ―― ヘアラインの上の傷を、照明の円錐の半角 α だけを 0.4〜8° で往復させて撮る。α ≈ β で筋が最も目立ち、α ≈ 4° で筋が消えて傷が黒く残り、広げすぎると傷も薄れる。
py -3.11 examples/poc_illumination_cone_vs_na.py
Source: examples/poc_illumination_cone_vs_na.py
This run produced 6 figures in total - see them all
Ops used (notes): blast_normals · light_source · metal_mirror_rgb · micro_normals · solid_angle_boundaries · solid_angle_brightness · solid_angle_light · solid_angle_render · solid_angle_sweep · text_box
Normalised cross-correlation, the usual template matcher, fails when part of the target is hidden or glare washes it out. Matchers comparing the sign of brightness changes were tested on 40 templates from public real photos. They withstand occlusion far better, yet every method stays weak to rotation.
| *↑ Matching on the Direction of Brightness Change — NCC Falls at 10 % Occlusion, Code Matching Holds to Over 60 % ―― Normalised cross-correlation (NCC) compares brightness values, so it is fragile when part of the template is occluded, saturated by a highlight or darkened on one side. Four matchers that compare the direction of brightness change as codes (orientation code matching, increment sign correlation, the selective correlation coefficient and the Radial Reach Filter, re-implemented from the published equations) are added as six codematch ops and scored on 40 templates of 32 x 32 cut from five public CC0 photographs. Half the hits are lost at 10 % occlusion for NCC, 36 % for SCC, 63 % for ISC and 67 % for OCM; at 6 %, 10 %, 44 % and 53 % saturated highlight. All four are weak to rotation (7-9 degrees). SCC selects only pixels whose codes agree, which inflates correlation at unrelated positions (selection bias); taken literally it broke before NCC, and a floor at the uncorrelated usage rate plus 4 sigma fixes it. On 11 flat sky templates NCC misses 10 with correlation around 0.82, while the code matchers refuse or answer correctly (no wrong answer). Gates: a brute-force second implementation, the existing NCC, invariance to monotone maps, commutation with rot90 (broken by a probe that swaps the cyclic difference for the absolute one) and a closed-form model of the values at the true position ( | z | <= 3.6 over 1,080 trials).* |
Terms: OCM — A template-matching method that quantizes each pixel’s gradient orientation and compares the resulting orientation codes.
↑ The measurement ―― 公開の実写 5 枚から切った 32 x 32 のテンプレートを、雑音 σ = 0.01 の場面で探す。点 = 段ごとの割合(各段 40 試行)。50 % を割る所が破綻点。 (figure labels are in Japanese; the numbers are the same)
↑ 公開の実写 5 枚から切った 32 x 32 のテンプレートを、雑音 σ = 0.01 の場面で探す。
↑ 公開の実写 5 枚から切った 32 x 32 のテンプレートを、雑音 σ = 0.01 の場面で探す。
↑ 空の平坦な所(標準偏差 < 0.01)を切ったテンプレート。
↑ 雑音の無い場面の増分 Δ と σ から、組ごとの一致確率 Φ(Δ/(√2σ)) が決まる。
↑ The animation ―― 飽和したハイライトを 0 → 60 % まで広げる(camera、CC0)。白 = 正解、橙 = NCC、青 = OCM、緑 = ISC、紫 = SCC。NCC は最初の数コマで光の縁へ飛ぶ。
py -3.11 examples/poc_code_matching_breakdown.py
Source: examples/poc_code_matching_breakdown.py
This run produced 10 figures in total - see them all
Ops used (notes): isc_match · ncc_locate · ocm_match · radial_reach_filter · reflect · scc_match · text_box
A classic inspection learns the usual per-pixel variation from good parts and flags what falls outside. Here the band is set by a false-alarm rate declared in advance and scored on 200 public real images. Alignment mattered more than the number of good parts, and the declared rate was not kept.
↑ Flagging What Differs from Aligned Good Samples — a Variation Model with a Declared False-Alarm Rate, and Where Real Images Break It ―― The inspection classic, learning every pixel’s usual value and spread from many good samples and flagging what leaves the band, as an op family whose band width is derived from a declared per-pixel false-alarm rate instead of being set by hand (five new varmodel_ ops plus four method-independent anomeval metrics). For normal good samples a new pixel’s deviation follows a t prediction interval, so the band width is closed-form. False-alarm rates counted on synthetic good samples fall inside the binomial 4-sigma band for per-pixel std, one pooled variance, empirical-Bayes shrinkage (Smyth 2004) and median/MAD, and the pooled variance fails when the noise differs per pixel. With five good samples the std band is 9.4 sigma wide (the t4 tail); shrinkage brings it to 5.0 and lifts the detection of 5-sigma flaws from 0.13 to 0.52. A good set shifted by 1-2 px is refused; forced through, the band on edges grows 22-fold and misses every edge flaw, and registering by a 1/20-px Fourier shift brings it back to 1.07. With three of twenty “good” samples carrying the same flaw, std misses the real flaw there (0.00) while median/MAD finds it (0.98). On the public VisA pcb1 data (CC BY 4.0, 200 test images) the registered band reaches pixel AUROC 0.808 and AUPRO 0.678 against 0.705 and 0.460 unregistered; golden comparison scores a higher pixel AUROC (0.826) but a lower AUPRO (0.519). The declared 1e-3 false-alarm rate is not kept on real images (3.8 %), and recalibrating on held-out good images only reaches 2.4 %: a difference between the training and test captures remains that a translation cannot remove. A stochastic texture (DAGM 2007, CC BY 4.0) has no structure in its mean image, its alignment is reported as unobservable and detection is at chance (pixel AUROC 0.465): where the method works and where it does not, both in numbers.*
↑ The measurement ―― 20 枚の良品のうち 3 枚の同じ場所(右上の四角)に +0.3 の傷。std は傷を良品のばらつきとして覚え、帯が太る(左上)ので、同じ場所の本物の傷(+0.2 = 雑音の 10 倍)の z が小さい。中央値と MAD は 3 枚の外れを無視する。左下の傷は汚れていない場所の対照。 (figure labels are in Japanese; the numbers are the same)
↑ VisA pcb1 の試験 200 枚(CC BY 4.0)。
↑ VisA pcb1(CC BY 4.0、灰色・1/4 縮小、欠陥の周りを切り出し、基板の印字は入れない)。
↑ 画素ごとに独立な正規の良品(合成)。
py -3.11 examples/poc_good_sample_variation_model.py
Source: examples/poc_good_sample_variation_model.py
This run produced 5 figures in total - see them all
Ops used (notes): anomaly_image_auroc · anomaly_pixel_auroc · anomaly_pro · anomaly_region_overlap · dc_rpca_sparse · varmodel_alignment · varmodel_calibrate · varmodel_fit · varmodel_score
The most common family in inspection research, learning the features of good samples and flagging patches that leave them, built with classical features (multi-resolution gradients, Gabor, rotation-invariant LBP, spectral bands, colour) instead of a deep network, with the variants compared on one yard-stick (pixel AUROC (area under the ROC curve), AUPRO (area under the per-region-overlap curve), image AUROC, F1 max). Eight new ops: patch_features, patchdist_fit / patchdist_score, patchbank_fit / patchbank_score, and the evaluation additions anomaly_pixel_f1_max, anomaly_image_f1_max and anomaly_operating_point. On a synthetic stochastic texture (smooth random fields with planted spots and thin scratches) the per-position normal distribution scores an AUPRO of 0.375 with 20 good images and loses to the pooled distribution (0.559) and a 10 % memory bank (0.603), in the same order at 5, 20 and 80 good images: it estimates 51 dimensions from few samples per position. Where n <= D makes the covariance singular the shrinkage is declared (intensity 0.374 at 5 good images, 0.067 at 80) and an undeclared one is refused. The existing variation model scores a pixel AUROC of 0.785 on the texture (the memory bank 0.881) and is refused on a weave whose phase differs every time. A logical anomaly, a vertical bar moved by 24 px, is missed by a memory that ignores position (anomaly score 0.9 times that of good images) and found when a position weight is added (3.9 times). Thinning the memory to 10 % keeps AUPRO at 0.638 against 0.684 and cuts the scoring CPU time from 12.55 s to 0.84 s. The operating point that guarantees recall 0.8 at confidence 0.9 keeps its promise in 0.957 of 300 repetitions of the calibrate-on-30-defects procedure, against 0.503 for simply taking the 80 % point of the calibration defects, at the price of more false alarms (0.369 against 0.211). On the public DAGM 2007 Class1 data (a stochastic texture, CC BY 4.0) the variation model scores a pixel AUROC of 0.436, below chance; a gradient-only memory bank reaches 0.768 (AUPRO 0.474) but its per-image AUROC is 0.53, and growing the feature to 51 dimensions drops it to 0.623. On VisA pcb1 (CC BY 4.0, 20 good images) the 10 % memory bank reaches pixel AUROC 0.990, AUPRO 0.875 and image AUROC 0.935 (the variation model: 0.808 and 0.678), and adding the position weight lowers it to 0.969 and 0.790; candle scores 0.895 and 0.734.
↑ Anomaly Detection from Good Samples Only — Classical Patch Features, a Per-Position Distribution Against a Memory Bank on One Yard-Stick, Down to a Recall-Guaranteeing Threshold ―― The most common family in inspection research, learning the features of good samples and flagging patches that leave them, built with classical features (multi-resolution gradients, Gabor, rotation-invariant LBP, spectral bands, colour) instead of a deep network, with the variants compared on one yard-stick (pixel AUROC (area under the ROC curve), AUPRO (area under the per-region-overlap curve), image AUROC, F1 max). Eight new ops: patch_features, patchdist_fit / patchdist_score, patchbank_fit / patchbank_score, and the evaluation additions anomaly_pixel_f1_max, anomaly_image_f1_max and anomaly_operating_point. On a synthetic stochastic texture (smooth random fields with planted spots and thin scratches) the per-position normal distribution scores an AUPRO of 0.375 with 20 good images and loses to the pooled distribution (0.559) and a 10 % memory bank (0.603), in the same order at 5, 20 and 80 good images: it estimates 51 dimensions from few samples per position. Where n <= D makes the covariance singular the shrinkage is declared (intensity 0.374 at 5 good images, 0.067 at 80) and an undeclared one is refused. The existing variation model scores a pixel AUROC of 0.785 on the texture (the memory bank 0.881) and is refused on a weave whose phase differs every time. A logical anomaly, a vertical bar moved by 24 px, is missed by a memory that ignores position (anomaly score 0.9 times that of good images) and found when a position weight is added (3.9 times). Thinning the memory to 10 % keeps AUPRO at 0.638 against 0.684 and cuts the scoring CPU time from 12.55 s to 0.84 s. The operating point that guarantees recall 0.8 at confidence 0.9 keeps its promise in 0.957 of 300 repetitions of the calibrate-on-30-defects procedure, against 0.503 for simply taking the 80 % point of the calibration defects, at the price of more false alarms (0.369 against 0.211). On the public DAGM 2007 Class1 data (a stochastic texture, CC BY 4.0) the variation model scores a pixel AUROC of 0.436, below chance; a gradient-only memory bank reaches 0.768 (AUPRO 0.474) but its per-image AUROC is 0.53, and growing the feature to 51 dimensions drops it to 0.623. On VisA pcb1 (CC BY 4.0, 20 good images) the 10 % memory bank reaches pixel AUROC 0.990, AUPRO 0.875 and image AUROC 0.935 (the variation model: 0.808 and 0.678), and adding the position weight lowers it to 0.969 and 0.790; candle scores 0.895 and 0.734.
Terms: AUROC — The area under the receiver-operating-characteristic curve, summarizing ranking performance across thresholds on a 0-to-1 scale.; ROC — A curve of true-positive rate against false-positive rate as the decision threshold varies, showing a binary detector’s trade-off.; AUPRO — An anomaly-localization metric integrating per-region overlap against false-positive rate over a specified false-positive range.
↑ The measurement ―― 記憶バンクの異常度閾値を下げると欠陥領域が現れる。 (figure labels are in Japanese; the numbers are the same)
↑ 確率場・良品 20 枚の PRO 曲線。
↑ 確率場の試験画像での AUPRO。
↑ 良品では縦の棒は左(x = 20)にある。
↑ 欠陥 400 枚のプールから較正に欠陥 30 枚を引いて閾値を決め、残りの欠陥で真の再現率を測った 300 回を小さい順に並べた。
py -3.11 examples/poc_patch_anomaly.py
Source: examples/poc_patch_anomaly.py
This run produced 11 figures in total - see them all
Ops used (notes): anomaly_image_auroc · anomaly_operating_point · anomaly_pixel_auroc · anomaly_pixel_f1_max · anomaly_pro · anomaly_region_overlap · gabor · intensity · patch_features · patchbank_fit · patchbank_score · patchdist_fit · varmodel_fit · varmodel_score
Overlapping bubbles show up as peaks of the distance transform, and as the neck between them gets shallower the image stops deciding whether there are two bubbles or one. The existing chain (blob_distance, blob_seeds, blob_split) picks one h and always returns one number, so it calls two circles at overlap 0.8 a single bubble. The new op blob_count_interval measures each peak’s prominence (0-dimensional persistence), returns an interval when the relative prominence falls inside a band, and opens the upper bound when the inscribed circles of the counted peaks cannot explain the area. The relative prominence of two equal circles follows the closed form 1 − sqrt(1 − (1 − o)²) to within 1.6 px, and upscaling by 2 leaves every answer unchanged. Confident errors appear only beyond the overlap where the union becomes indistinguishable from an ellipse of aspect 1.1 (0.86 to 0.90 by the closed form). On the BBBC005 ground-truth masks (CC0) the small objects are counted with a relative error of 0.34 % (connected components 9.4 %); for the deeply overlapping large objects the op declines to give a number on 286 of 300 images and makes no confident error. Semi-synthetic clusters built from real oxygen-bubble shapes of a water electrolyser (PEMWE, CC BY 4.0, hand-drawn masks) give 2 confident errors in 65 scenes and an ALCON2009-style score (centroid inside each bubble’s box, implemented here) of 0.95 on the clusters it answers. Widening the shape tolerance until 95 % of isolated bubbles pass lets hidden bubbles through and raises the error rate from 3 % to 19 % — the sweep shows the trade-off between counting more and never being silently wrong. When irregular bubbles overlap deeply, even the upper bound of the declined interval can miss by one; the op documents this limit.
↑ Counting Dense Bubbles — When the Overlap Is Too Deep to Decide, Return an Interval Instead of a Count ―― Overlapping bubbles show up as peaks of the distance transform, and as the neck between them gets shallower the image stops deciding whether there are two bubbles or one. The existing chain (blob_distance, blob_seeds, blob_split) picks one h and always returns one number, so it calls two circles at overlap 0.8 a single bubble. The new op blob_count_interval measures each peak’s prominence (0-dimensional persistence), returns an interval when the relative prominence falls inside a band, and opens the upper bound when the inscribed circles of the counted peaks cannot explain the area. The relative prominence of two equal circles follows the closed form 1 − sqrt(1 − (1 − o)²) to within 1.6 px, and upscaling by 2 leaves every answer unchanged. Confident errors appear only beyond the overlap where the union becomes indistinguishable from an ellipse of aspect 1.1 (0.86 to 0.90 by the closed form). On the BBBC005 ground-truth masks (CC0) the small objects are counted with a relative error of 0.34 % (connected components 9.4 %); for the deeply overlapping large objects the op declines to give a number on 286 of 300 images and makes no confident error. Semi-synthetic clusters built from real oxygen-bubble shapes of a water electrolyser (PEMWE, CC BY 4.0, hand-drawn masks) give 2 confident errors in 65 scenes and an ALCON2009-style score (centroid inside each bubble’s box, implemented here) of 0.95 on the clusters it answers. Widening the shape tolerance until 95 % of isolated bubbles pass lets hidden bubbles through and raises the error rate from 3 % to 19 % — the sweep shows the trade-off between counting more and never being silently wrong. When irregular bubbles overlap deeply, even the upper bound of the declined interval can miss by one; the op documents this limit.
↑ The measurement ―― 等しい 2 円の 2 番目の山の相対突出度。閉形式(破線)は半径によらない。実測は格子の量子化で閉形式より最大 1.5 px 低い。点線の帯の中に落ちた山は『あいまい』。 (figure labels are in Japanese; the numbers are the same)
↑ 重なり率を上げると、くびれが浅くなって 0.5 前後で『1 個か 2 個か』が決まらなくなり(橙)、0.7 を超えると山が 1 つになる。
↑ world_blobs_touching(12 円、半径 14 px)。
↑ BBBC005 の真値マスク。
↑ 緑の矩形 = 置いた泡(真値)、赤の十字 = 決まった塊の重心。
↑ The animation ―― 2 つの泡が近づいて融合していく。色 = 距離変換(内接円の半径)、白い十字 = 確かな山、橙 = あいまいな山。くびれが浅くなると 2 番目の山が帯に落ち、やがて消える。
py -3.11 examples/poc_dense_bubble_count.py
Source: examples/poc_dense_bubble_count.py
This run produced 12 figures in total - see them all
Ops used (notes): blob_count_interval · blob_distance · blob_features · blob_label · blob_seeds · blob_split · text_box · world_blobs_touching
A low-magnification objective sees a wide field but misses fine detail. Combining images lit from different angles in frequency space resolved detail as fine as a 20x image over the same wide field. When the illumination overlap is too small to converge, no answer is returned.
↑ Fourier Ptychography — 20x-Equivalent Resolution and Phase from a 4x Objective’s Wide Field, by Iterative Phase Retrieval ―― Keep the wide field of a low-magnification objective, take a set of images under LED illumination from many angles, stitch them in frequency space and recover a 20x-equivalent resolution and the phase by iterative phase retrieval. Six new ops (fourierptycho, category fpm of opsoptics): closed-form design numbers before measuring (synthetic NA, cut-off period, pupil overlap, LED count, sampling, LEDs on the bright-/dark-field boundary), a USAF target, a capture simulator (noise, aberrations, LED misalignment, reflective heights), an entry point for measured data, a reconstruction with joint pupil recovery and LED position correction that returns its convergence history, stop reason and synthetic NA and refuses unless it converges, and an element-by-element USAF read-out. With a 4x / NA 0.1 objective and 13 x 13 LEDs (synthetic NA 0.424) one image resolves G7 E4 (5.53 um; closed form 5.32 um) and FP resolves G9 E5 (1.22 um; closed form 1.25 um), the same element as a 20x / NA 0.40 coherent image. Phase within 0.13 rad rms of the band-limited truth; reflective 20-40 nm steps within 4.0 nm rms. Lowering the pupil overlap from 64 % to 7 %, 43 % and above converge while 32 % and below are refused, bracketing the published 35 % minimum; the residual alone cannot tell (18 % overlap has a smaller residual than 64 %). Pupil aberrations of 0.12 waves rms are recovered to 0.005 waves and an LED array offset of (0.6, -0.4) mm, 1 deg and 1.5 mm is read back as (0.62, -0.36) mm, 0.93 deg and 1.25 mm. Flipping the sign of k yields the phase-conjugate image with the same residual. On public FP data of a USAF target (not shipped; run only when present) FP resolves G9 E3 (1.55 um) against the closed-form 1.60 um from the published set-up; a single raw image resolves G7 E3. Nothing learned, nothing calibrated, 33 gates.
↑ The measurement ―― G9(周期 1.95〜1.06 µm)を 3 倍に拡大。FP は E5(1.22 µm)まで 3 本の棒が分かれる。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ 瞳の円(NA 0.1)の中の位相。
↑ LED アレイが (0.6, −0.4) mm・1°・高さ 1.5 mm ずれている。
py -3.11 examples/poc_fourier_ptychography.py
Source: examples/poc_fourier_ptychography.py
This run produced 11 figures in total - see them all
Ops used (notes): coherent_image · fp_capture · fp_design · fp_element_contrast · fp_reconstruct · fp_stack_from_images · fp_usaf_target
Stack moving vehicles and fixed pillars along time and you get a braid; two plans that deform into each other with their ends held fixed are exactly the plans with the same braid. The times at which neighbouring strands swap order along a projection axis are solved in closed form into a signed crossing sequence (a braid word), and three independent deciders — Dynnikov coordinates, Dehornoy handle reduction and the Artin representation — decide whether two words are the same braid (background: homotopy in planar multi-agent path planning, doi:10.1613/jair.1.19243). The figure spins three differently wound plans of three robots as space-time braids. Gates: the braid relations hold under the Dynnikov action in 1311 cases with integer error 0 and real error 2.8e-14, while the non-relation σ₁σ₂ = σ₂σ₁ breaks in 100/100. The three deciders agree on 100/100 equal and 100/100 different braids; Dynnikov coordinates match the MIT-licensed reference implementation in 240/240. One robot around a pillar: shortest cost per class [14, 14, 30, 30, 46] = closed form 14 + 16m. 61 candidates from the agvfleet planners (CBS + prioritised with waypoints) fall into 26 classes, and the CBS optimum 40 represents the cheapest one. A commutator a b a⁻¹ b⁻¹ has all windings 0 yet a non-trivial braid. Trap: point-symmetric grid moves create projection triple points that no change of angle removes — a tiny constant per-strand offset resolves them. Honestly: there are infinitely many classes, and only those the candidate sampler finds are listed (waypoint sampling found 3 of 8).
↑ Sorting Multi-Robot Plans with the Same Starts and Goals by How They Wind — Counting Homotopy Classes with Space-Time Braids and Dynnikov Coordinates, 61 Planner Candidates in 26 Classes ―― Stack moving vehicles and fixed pillars along time and you get a braid; two plans that deform into each other with their ends held fixed are exactly the plans with the same braid. The times at which neighbouring strands swap order along a projection axis are solved in closed form into a signed crossing sequence (a braid word), and three independent deciders — Dynnikov coordinates, Dehornoy handle reduction and the Artin representation — decide whether two words are the same braid (background: homotopy in planar multi-agent path planning, doi:10.1613/jair.1.19243). The figure spins three differently wound plans of three robots as space-time braids. Gates: the braid relations hold under the Dynnikov action in 1311 cases with integer error 0 and real error 2.8e-14, while the non-relation σ₁σ₂ = σ₂σ₁ breaks in 100/100. The three deciders agree on 100/100 equal and 100/100 different braids; Dynnikov coordinates match the MIT-licensed reference implementation in 240/240. One robot around a pillar: shortest cost per class [14, 14, 30, 30, 46] = closed form 14 + 16m. 61 candidates from the agvfleet planners (CBS + prioritised with waypoints) fall into 26 classes, and the CBS optimum 40 represents the cheapest one. A commutator a b a⁻¹ b⁻¹ has all windings 0 yet a non-trivial braid. Trap: point-symmetric grid moves create projection triple points that no change of angle removes — a tiny constant per-strand offset resolves them. Honestly: there are infinitely many classes, and only those the candidate sampler finds are listed (waypoint sampling found 3 of 8).
↑ The measurement ―― 図 01 の最初のコマ(等倍・可逆 PNG)。 (figure labels are in Japanese; the numbers are the same)
↑ 同じ 3 つの計画を床の上から見る(× は柱の代表の点)。
↑ 1 台が柱を避けて左から右へ: 類ごとの最短経路を短い順に 5 本(費用 14・14・30・30・46 = 14 + 16m)。
↑ 候補を類に分けた表(費用の安い順)。
py -3.11 examples/poc_braid_homotopy_classes.py
Source: examples/poc_braid_homotopy_classes.py
This run produced 5 figures in total - see them all
Ops used (notes): braid_class_representatives · braid_equivalent · braid_from_trajectories · braid_reduce · dynnikov_act · dynnikov_coordinates · grid_distances · grid_hole_points · grid_paths_to_xy · homotopy_class_compare · homotopy_shortest_paths · mapf_cbs · mapf_prioritized · pairwise_winding · plan_conflicts · plan_cost
Four Noll-normalised aberrations are swept over 484 conditions. Fixed-axis intensity ratio, same-plane peak ratio and peak location are defined separately. Coma at 0.2/0.3 waves gives fixed-axis ratios 0.1816/0.0127, peak ratios 0.2178/0.1437 and peak shifts of 8/17 pixels. The 5 percent Marechal boundaries quoted are for the fixed-axis definition. Mahajan (1982/1983) already reported aberration-specific results; this PoC is a definition and implementation reproduction.
↑ The Marechal Approximation Breaks at Different RMS Errors for Different Aberration Shapes ―― Four Noll-normalised aberrations are swept over 484 conditions. Fixed-axis intensity ratio, same-plane peak ratio and peak location are defined separately. Coma at 0.2/0.3 waves gives fixed-axis ratios 0.1816/0.0127, peak ratios 0.2178/0.1437 and peak shifts of 8/17 pixels. The 5 percent Marechal boundaries quoted are for the fixed-axis definition. Mahajan (1982/1983) already reported aberration-specific results; this PoC is a definition and implementation reproduction.
py -3.11 examples/poc_wavefront_aberration_limits.py
Source: examples/poc_wavefront_aberration_limits.py
This run produced 1 figures in total - see them all
Ops used (notes): pupil_psf
The design matrix formed by 0, 45, 90 and 135 degree linear micro-analysers has SVD-computed rank 9 of 16; the number is not hard-coded. Adding quarter-wave-plate states on generation and analysis raises the rank to 16. Fullseye’s Mueller chain and an independent Kronecker design equation agree within 1.39e-17. Across 200 trials with 0.2 percent read noise, median relative Mueller error falls from 41.64 percent for linear-only measurements to 1.40 percent with the QWP states.
↑ A Four-Angle Polarization Camera Leaves Seven Mueller (4-by-4 polarization-transfer matrix) Dimensions Unseen ―― The design matrix formed by 0, 45, 90 and 135 degree linear micro-analysers has SVD-computed rank 9 of 16; the number is not hard-coded. Adding quarter-wave-plate states on generation and analysis raises the rank to 16. Fullseye’s Mueller chain and an independent Kronecker design equation agree within 1.39e-17. Across 200 trials with 0.2 percent read noise, median relative Mueller error falls from 41.64 percent for linear-only measurements to 1.40 percent with the QWP states.
Terms: Mueller matrix — A real 4-by-4 matrix acting on a Stokes vector to describe how an optical element or sample changes polarization.
py -3.11 examples/poc_mueller_dofp_observability.py
Source: examples/poc_mueller_dofp_observability.py
This run produced 1 figures in total - see them all
Ops used (notes): mueller_element
For a 3.45 um pixel, F/5.6 and 550 nm system, the circular-pupil Airy OTF is applied to a high-resolution edge before pixel-area integration, so forward model and truth agree over the full frequency range. Central-difference correction is 1 at DC and 1.1107207 at Nyquist. Across 420 conditions the remaining clean linear estimator error is at most 0.00872 cycles per pixel, displayed separately from zero model error; failures beyond 10 percent begin at 1.0 degree.
↑ Slanted-Edge SFR Misreads MTF50 (spatial frequency at 50% MTF) Through Gamma as Well as Edge Angle ―― For a 3.45 um pixel, F/5.6 and 550 nm system, the circular-pupil Airy OTF is applied to a high-resolution edge before pixel-area integration, so forward model and truth agree over the full frequency range. Central-difference correction is 1 at DC and 1.1107207 at Nyquist. Across 420 conditions the remaining clean linear estimator error is at most 0.00872 cycles per pixel, displayed separately from zero model error; failures beyond 10 percent begin at 1.0 degree.
Terms: MTF50 — The spatial frequency at which MTF falls to 50% of its low-frequency value, used as a single-number resolution summary.; MTF — The magnitude of the optical transfer function, showing how image contrast is transferred at each spatial frequency.; Nyquist — The highest frequency a sampling grid can represent: half a cycle per sample. Finer patterns fold back and appear as a different, coarser pattern.
py -3.11 examples/poc_slanted_edge_sfr_limits.py
Source: examples/poc_slanted_edge_sfr_limits.py
This run produced 1 figures in total - see them all
Ops used (notes): mtf_diffraction
To state that a part is 50.50 pixels wide, you need bias (the part that always shifts the same way) and scatter (the part that changes from shot to shot) as two separate numbers. Pass/fail is decided by bias; repeatability by scatter. Merge them into one ‘error’ and you no longer know which countermeasure to take.
The 45 exhibits here hold their ground truth in closed form or analytic rendering — a signed-distance-function part, an involute gear, a roughness surface synthesised from a prescribed PSD, a white-light interferometry stack, analytic speckle, Frocht’s stress field, a perfectly symmetric synthetic skull — and then score caliper, correlation and phase readings against it.
The recurring finding is that a number without its definition cannot be compared: crack widths that differ by 0.20 mm between two distance-transform conventions, a D50 that differs by 1.66x between number- and area-weighting, an Sz that never plateaus as the evaluation area grows, an orientation index that moves 5 % depending on whether the truth is counted by fibre or by length. These are not instrument errors; they are questions of what you are comparing against.
Full visual inspection and sampled CT must assign the same index to each joint. Unequal-sided fiducials fixed this correspondence, then top-ranked candidates caught 19 % of interface-touching voids. Apparent void rankings can agree while most life-limiting parts are missed.
↑ Matching Full 2-D Inspection to Sampled 3-D Inspection — A Grid Overlaps Itself ―― The real line has two stages: AOI images every unit from above, X-ray CT samples a few and images the inside. What the floor wants to know is how far the CT numbers follow from the AOI numbers — which is not a question about one instrument’s accuracy but about whether two instruments’ indices point at the same joint. A regular array does not match one-to-one on point positions alone: all 30 pads of a 6x5 grid have degenerate distance signatures (9 distinct ones), because the grid maps onto itself and the orientation cannot be read. Fiducials with three distinct side lengths (3600/5200/6325 um) pin rotation and reflection uniquely, and the correspondence is recovered exactly under translation, rotation and re-indexing. With that closed, AOI’s apparent void fraction correlates 0.973 with CT volume fraction and the worst five agree 5/5 — yet of the 27 parts carrying an interface-touching void (the kind that shortens life), those five catch only 19 %. Agreeing on the ranking does not mean catching the dangerous ones. The failed first attempt (Kabsch alone) is kept.
↑ The measurement ―― Matched crop of one joint: surface appearance and internal reconstructed void at the same coordinates. (figure labels are in Japanese; the numbers are the same)
↑ AOI は全数・2-D、CT は抜き取り・3-D。
↑ 直線は「面積率に比例する」という素朴な仮定。
↑ AOI の面積率が大きい接合部が、界面に接するボイドを持つとは限らない。
↑ 同じロットを同じ「悪い順」で並べても、順位は一致しない。
py -3.11 examples/poc_aoi_ct_traceability.py
Source: examples/poc_aoi_ct_traceability.py
This run produced 6 figures in total - see them all
Ops used (notes): filtered_backprojection · radon_transform · vol_label
Point-cloud dimensions can confuse room heading or furniture with wall position. A room with known tilt and bulges was rotated, while its plane-pair error stayed at +2.24 mm. Bounding boxes grow with heading and sample count; plane fitting jumps to furniture when outliers become the majority.
↑ As-built wall deviation — the bounding box measures the room’s heading, not its size ―― A 6.0 x 4.0 x 2.7 m indoor point cloud carries known defects: wall plumb errors of 1.20-9.00 mrad, a 5.00 mrad plan-view squareness error, and a 9 mm out-of-plane bulge. The naive axis-aligned bounding box over-reports the width by 19.1 mm, and rotating the room by just 1 deg relative to the scanner axes adds 80.1 mm (611.1 mm at 10 deg) — it is measuring W cos psi + D sin psi, the room’s heading, while the plane-pair distance stays at +2.24 mm regardless. The AABB also grows with sample size (+14.9 to +19.7 mm for 256x more points, the 2-sigma-sqrt(2 ln N) extreme-value law), so adding measurements makes it worse. Furniture outliers break the two estimators differently: least squares is off by 11x the truth at 10 % contamination (66.3 mrad vs a closed-form 63.8), while RANSAC holds to 45 % and then jumps onto the cabinet at 55 % — where the plumb error stays a harmless 0.13 mrad but the plane itself is 449.9 mm out, so one number hides the failure. A single bulge corrupts plumb (-1.258 mrad), squareness (+0.500 mrad) and the internal dimension (+2.2 mm) at once, all predicted in closed form to within 0.06 mrad.
↑ The measurement ―― 許容 ±10 mm。AABB は ψ=0.5 度で既に外れる。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ この回の図
↑ この回の図
py -3.11 examples/poc_asbuilt_wall_deviation.py
Source: examples/poc_asbuilt_wall_deviation.py
This run produced 12 figures in total - see them all
Ops used (notes): aabb · angle_between_planes · angle_line_plane · distance_point_plane · fit_plane3 · ransac_plane
Slight electrode growth must be separated from brightness changes between scans. Layer boundaries were tracked across two cross-sections, recovering a stack increase of +2.136 µm. A fixed brightness crossing reports false strain after an offset, whereas the gradient peak remains at zero.
↑ Measuring electrode breathing in microns — sub-pixel is not enough ―― Electrode swelling is read from two cross-sections by tracking layer boundaries to sub-pixel accuracy. Ground truth: anode 1.00 %, cathode 0.20 %, separator 0 %, stack 480.0 -> 482.136 um (+2.136 um = +0.4450 %). Counting binarised pixels resolves 0 of 12 layers, while a fixed-threshold crossing is also sub-pixel and lands on the answer with no noise (+0.004495) — what kills it is the control group: with zero strain, an illumination offset of +0.10 alone yields -1.07e-02, a false strain 2.4x the true one and of the opposite sign, and a gain of x1.30 halves the crossings so the measurement fails outright. The gradient-peak estimator (measure_pos) stays at exactly 0.0 under both. Doubting our own ‘1.8e-14 px error’ and shifting the stack by fractions of a pixel showed that figure to be an artefact of putting the truth on pixel centres: the real error is peak locking, RMS 0.0088 / max 0.0122 px, worth 3.0 % on the anode’s strain. The cliff has two steps, and the one where boundary counts collapse arrives 5.5x earlier than the predicted 3-sigma limit.
↑ The measurement ―― 境界は erf の重ね合わせで解析的に置いてあるので、真値は浮動小数点の精度で既知。 (figure labels are in Japanese; the numbers are the same)
↑ 階段状に増えるのは、伸びる層(負極 1.00 %)と伸びない層(セパレータ 0 %)が交互だから。
↑ 真値を画素の中心に置いた検査は、推定器を実力以上に良く見せる。
↑ 横線は真値。
↑ 実測が予測から離れる左端は、雑音が増えたのではなく境界を数え損ねている。
py -3.11 examples/poc_battery_electrode_breathing.py
Source: examples/poc_battery_electrode_breathing.py
This run produced 9 figures in total - see them all
Ops used (notes): auto_threshold · gen_measure_rectangle2 · measure_pos · piv_peak_locking
A reference plane for left-right asymmetry may be pulled toward a one-sided deformation. A known bulge was added to a symmetric skull; the best-fit plane moved by 2.92 mm. The optimized plane shifts toward the bulge and removes part of the asymmetry that should be reported.
↑ Measuring Bilateral Asymmetry — The Symmetry Plane Gets Dragged by the Deformation ―― A perfectly symmetric synthetic skull with a known bulge added on one side, mirrored and overlaid. Even a perfectly symmetric specimen never scores 0: the floor drops from 1.33 mm (point-to-point) to 0.030 mm (point-to-plane) to 0.012 mm (neighbourhood smoothing). The residual-minimising plane is dragged 2.92 mm / 1.72 degrees by a 6.33 mm bulge, and 46 % of the asymmetry disappears.
↑ The measurement ―― 完全対称な標本を測った残差。点対点は点間隔がそのまま床になる。 (figure labels are in Japanese; the numbers are the same)
↑ 真値の線は利得 1.0。
↑ margin < 0.1 で採用を止めれば、66〜88 度の外しは弾ける。
py -3.11 examples/poc_bilateral_asymmetry.py
Source: examples/poc_bilateral_asymmetry.py
This run produced 4 figures in total - see them all
Ops used (notes): chamfer_distance · detect_reflection_symmetry · detect_rotational_symmetry · estimate_normals · hausdorff_distance · reflect_points · reflection_symmetry_score · sample_surface · vertex_normals
Displacement and strain read from a speckle pair in which 3000 Gaussian spots were moved by the deformation map and redrawn, not interpolated. For a true shift of 0.37 px the window-correlation estimator (piv) is biased by 0.0002 px with 0.0022 px scatter, 167x better than the null that answers ‘no motion’. A rigid rotation manufactures several hundred µε of false strain under the small-strain definition; Green-Lagrange gives exactly 0.
↑ Strain From Speckle Images (DIC) ―― Displacement and strain read from a speckle pair in which 3000 Gaussian spots were moved by the deformation map and redrawn, not interpolated. For a true shift of 0.37 px the window-correlation estimator (piv) is biased by 0.0002 px with 0.0022 px scatter, 167x better than the null that answers ‘no motion’. A rigid rotation manufactures several hundred µε of false strain under the small-strain definition; Green-Lagrange gives exactly 0.
Terms: DIC — A method that correlates local surface patterns before and after deformation to measure displacement and derive strain.
↑ The measurement ―― 変形は補間ではなく斑点の再描画。だから真値が厳密。 (figure labels are in Japanese; the numbers are the same)
↑ lk は 0.5 px 側へ寄る(教科書の peak locking と逆)。
↑ 窓を広げると尖頭が下がり幅が広がる(空間分解能の限界)。
↑ The animation ―― 引張試験の荷重を 48 段で上げる過程(lk、窓 31)。真のひずみを 0 → 3000 µε、同時に試験機が 0 → 2.0 度回る。変形像は毎段、斑点を写して描き直す(補間なし)。最終段で微小ひずみ ∂u/∂x の領域平均は 2341 µε(理論 (1+e)cosθ-1 = 2389 µε)—— 材料は 3000 µε 伸びているのに、回転が約 611 µε 少なく見せる。Green-Lagrange は 2961 µε(理論 e+e²/2 = 3005 µε)。地図の色は全コマ共通の尺度。
py -3.11 examples/poc_dic_strain.py
Source: examples/poc_dic_strain.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · piv_cross_correlate · plot_series · strain_from_displacement · text_box · ticks
Two stacked dies carry a 7x7 TSV lattice with a planted overlay of (+0.800, -0.450) um, +0.01500 deg of rotation and a die tilt of (1.20, 0.70) deg; a single CT volume is the whole measurement. Comparing the top-surface via openings directly misses by 2.419 um — 2.4x the +-1.0 um spec, and larger than the 0.918 um real overlay. The bias matches the closed form ‘die thickness x lateral part of the top-surface normal’ to a ratio of 0.996 / 0.999, and re-projecting each via down its own fitted axis recovers the truth to 0.0023 um. The prediction that failed: tilt fakes rotation as well as translation, through the sin(alpha)sin(beta) shear of Rx Ry — predicted +0.00733 deg against +0.00696 deg measured as the difference from the zero-tilt control. Axis correction does not remove it; only de-projecting with the measured tilt does. The cliff lands at 0.492 deg measured against 0.491 deg predicted.
↑ Die tilt and TSV overlay from one CT — tilt fakes rotation too ―― Two stacked dies carry a 7x7 TSV lattice with a planted overlay of (+0.800, -0.450) um, +0.01500 deg of rotation and a die tilt of (1.20, 0.70) deg; a single CT volume is the whole measurement. Comparing the top-surface via openings directly misses by 2.419 um — 2.4x the +-1.0 um spec, and larger than the 0.918 um real overlay. The bias matches the closed form ‘die thickness x lateral part of the top-surface normal’ to a ratio of 0.996 / 0.999, and re-projecting each via down its own fitted axis recovers the truth to 0.0023 um. The prediction that failed: tilt fakes rotation as well as translation, through the sin(alpha)sin(beta) shear of Rx Ry — predicted +0.00733 deg against +0.00696 deg measured as the difference from the zero-tilt control. Axis correction does not remove it; only de-projecting with the measured tilt does. The cliff lands at 0.492 deg measured against 0.491 deg predicted.
↑ The measurement ―― 雲ごと 2.42 µm ずれるのが傾きの偽装。散らばりの広がりは測定精度。 (figure labels are in Japanese; the numbers are the same)
↑ 偽の回転の予測 +0.00733°、倍率の予測 -147.0 ppm(対照群との差で実測 +0.00696° / -144.5 ppm)。
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_die_tilt_tsv_overlay/04_tilt_cliff.png) |
| *↑ ゼロ点(青)と予測(赤)は重なっている —— 誤差はダイ厚 x | 法線の横成分 | そのもの。* |
↑ 真の回転 0.01500° を超えるのは α ≈ 1.2°。
py -3.11 examples/poc_die_tilt_tsv_overlay.py
Source: examples/poc_die_tilt_tsv_overlay.py
This run produced 5 figures in total - see them all
Ops used (notes): fit_line3 · procrustes_fit · vol_label
A part defined by a signed distance function (slot width 50.50 px, 1 px = 12.5 µm), imaged through a known PSF (point spread function) and noise and measured by four caliper systems. Otsu’s integer width (the null) has an RMS error of 0.464 px = 5.8 µm; the buried 1-D measuring implementation is biased by -0.0113 px = -0.14 µm, 41x better. Once the edge spacing drops below 3.09 PSF widths the width comes out systematically large, and the API keeps returning success.
↑ Dimensional Inspection of a Machined Part — Bias and Scatter as Two Numbers ―― A part defined by a signed distance function (slot width 50.50 px, 1 px = 12.5 µm), imaged through a known PSF (point spread function) and noise and measured by four caliper systems. Otsu’s integer width (the null) has an RMS error of 0.464 px = 5.8 µm; the buried 1-D measuring implementation is biased by -0.0113 px = -0.14 µm, 41x better. Once the edge spacing drops below 3.09 PSF widths the width comes out systematically large, and the API keeps returning success.
Terms: PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.; Otsu — A histogram thresholding method that chooses the threshold maximizing the variance between the two resulting classes.
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_dimensional_inspection/02_blur_cliff.png) |
| *↑ The measurement ―― 偏りはどちらも正(対が互いを押し広げる)。符号が一定なので繰り返し測っても消えない。下端 -4 は表示の打ち切り( | 偏り | < 1e-4 px)。 (figure labels are in Japanese; the numbers are the same)* |
↑ 偏りは SNR 140 まで動かず、増えるのは散らばりだけ。
↑ 3 本の縦線は 16.0 px = 200 um 離れている。
py -3.11 examples/poc_dimensional_inspection.py
Source: examples/poc_dimensional_inspection.py
This run produced 4 figures in total - see them all
Ops used (notes): add_metrology_object_circle_measure · add_metrology_object_ellipse_measure · add_metrology_object_generic · add_metrology_object_line_measure · add_metrology_object_rectangle2_measure · align_metrology_model · apply_metrology_model · create_metrology_model · edge_points · ellipse · fuzzy_measure_pairing · gen_measure_arc · gen_measure_rectangle2 · m1_measure_pairs · m1_measure_pos · measure_pairs · measure_pos · otsu · translate_measure
Orientation of 140 fibres drawn from a von Mises distribution, read with a structure tensor. For a true mean of 177.9 degrees the arithmetic mean reports 105.55 degrees (-72.33), while the doubled-angle circular mean is off by +0.17 degrees — with not one bit of the image or the measurement changed. Weighting every pixel equally drops the orientation index by -31.3 %.
↑ Fibre Orientation Distribution — Angles Repeat Every 180 Degrees, and a Naive Mean Is 90 Degrees Off ―― Orientation of 140 fibres drawn from a von Mises distribution, read with a structure tensor. For a true mean of 177.9 degrees the arithmetic mean reports 105.55 degrees (-72.33), while the doubled-angle circular mean is off by +0.17 degrees — with not one bit of the image or the measurement changed. Weighting every pixel equally drops the orientation index by -31.3 %.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ この回の図
↑ この回の図
py -3.11 examples/poc_fiber_orientation.py
Source: examples/poc_fiber_orientation.py
This run produced 6 figures in total - see them all
Ops used (notes): coherence · dc_structure_texture · moment_axes · principal_moments · smooth_funct_1d_gauss · sobel_amp · sobel_dir
Eccentricity and tooth profile read from a gear drawn with the closed-form involute. The null’s least-squares circle has a diameter of 47.278 mm, which is neither the pitch (48), tip (52) nor root (43) circle. One missing tooth turns an eccentricity of 0.050 mm into 0.1285 mm (+157 %); the traditional method of one sample per tooth returns 0.0501 mm (+0.2 %).
↑ Gear Tooth Metrology — Eccentricity Is Order 1, Teeth Are Order z ―― Eccentricity and tooth profile read from a gear drawn with the closed-form involute. The null’s least-squares circle has a diameter of 47.278 mm, which is neither the pitch (48), tip (52) nor root (43) circle. One missing tooth turns an eccentricity of 0.050 mm into 0.1285 mm (+157 %); the traditional method of one sample per tooth returns 0.0501 mm (+0.2 %).
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ 歯の次数 24/48/72 は 2.6 mm あるので 0.35 mm で切った
↑ この回の図
↑ この回の図
↑ この回の図
py -3.11 examples/poc_gear_tooth_metrology.py
Source: examples/poc_gear_tooth_metrology.py
This run produced 6 figures in total - see them all
Ops used (notes): blob_boundaries · blob_features · blob_label · blob_overlay · blob_region · blob_select_largest · gauss_filter · polar_trans_image · spectrum · threshold
Grains are synthesized as a 2-D Voronoi tessellation with 2-px boundaries, then etching unevenness, noise and boundary gaps are added; ASTM E112 grain size G is measured by the planimetric method (Otsu + connected components) and the lineal-intercept method (local threshold + test lines in 4 directions). The planimetric method survives noise alone (-0.02) and etching unevenness alone (-0.40) but dies under both (+3.82), and loses one G step at 7.2 % boundary gaps; the intercept method holds to 40.7 % — the predicted 29.3 % was wrong because only 0.76 f of the boundary actually disappears in the mask. A duplex structure gives a whole-field G of 7.82 that matches neither the fine (9.01) nor the coarse (6.15) population; only 4 of 64 tiles fall within ±0.5 of it.
↑ Metallographic Grain Size — The Planimetric and Intercept Methods Fall Off Different Cliffs ―― Grains are synthesized as a 2-D Voronoi tessellation with 2-px boundaries, then etching unevenness, noise and boundary gaps are added; ASTM E112 grain size G is measured by the planimetric method (Otsu + connected components) and the lineal-intercept method (local threshold + test lines in 4 directions). The planimetric method survives noise alone (-0.02) and etching unevenness alone (-0.40) but dies under both (+3.82), and loses one G step at 7.2 % boundary gaps; the intercept method holds to 40.7 % — the predicted 29.3 % was wrong because only 0.76 f of the boundary actually disappears in the mask. A duplex structure gives a whole-field G of 7.82 that matches neither the fine (9.01) nor the coarse (6.15) population; only 4 of 64 tiles fall within ±0.5 of it.
Terms: Otsu — A histogram thresholding method that chooses the threshold maximizing the variance between the two resulting classes.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 面積法は 1 本の途切れで 2 粒が融合するので、切片法の 6 分の 1 の途切れで 1 段落ちる。
↑ この回の図
↑ この回の図
py -3.11 examples/poc_metal_grain_size.py
Source: examples/poc_metal_grain_size.py
This run produced 11 figures in total - see them all
Ops used (notes): bin_threshold · blob_features · blob_label · blob_overlay · bothat · dyn_threshold · gray_bothat · hx_close_edges · invert_image · otsu · threshold
In multibeam echosounding, a wrong sound-speed profile bends a flat bottom. Using surface sound speed directly also shifts nadir by +0.670 m; after calibrating mean sound speed at nadir, nadir error is zero but −2.697 m remains at 65 degrees. IHO S-44 Order 1a TVU at 50 m is 0.8201 m. The pre-fix TVU breach attributed to layer spacing was actually an omitted first-layer Snell conversion; after preserving the ray parameter from the first layer, the midpoint iso-velocity approximation is −0.016 m with two layers at 65 degrees, converges at about second order, and is within TVU. In this synthetic model amplitude-peak bias grows on outer beams, consistent with an advantage of phase detection, but it does not establish the design history of real instruments. The truth-free check implemented here is overlap comparison between adjacent lines (2.697 m at the edge). Under upward refraction, beams beyond the critical angle disappear while the reachable 65-degree beam still has +5.919 m depth error. A constant-gradient fit reduces the maximum error by 77%, from 5.357 m to 1.253 m, but leaves a TVU-breaching residual and worsens nadir by +0.262 m.
↑ Why the Seafloor Smiles — Sound-Speed Error Remaining on Outer Beams After Nadir Calibration ―― In multibeam echosounding, a wrong sound-speed profile bends a flat bottom. Using surface sound speed directly also shifts nadir by +0.670 m; after calibrating mean sound speed at nadir, nadir error is zero but −2.697 m remains at 65 degrees. IHO S-44 Order 1a TVU at 50 m is 0.8201 m. The pre-fix TVU breach attributed to layer spacing was actually an omitted first-layer Snell conversion; after preserving the ray parameter from the first layer, the midpoint iso-velocity approximation is −0.016 m with two layers at 65 degrees, converges at about second order, and is within TVU. In this synthetic model amplitude-peak bias grows on outer beams, consistent with an advantage of phase detection, but it does not establish the design history of real instruments. The truth-free check implemented here is overlap comparison between adjacent lines (2.697 m at the edge). Under upward refraction, beams beyond the critical angle disappear while the reachable 65-degree beam still has +5.919 m depth error. A constant-gradient fit reduces the maximum error by 77%, from 5.357 m to 1.253 m, but leaves a TVU-breaching residual and worsens nadir by +0.262 m.
↑ The measurement ―― エコーは beamform_delay_sum の角度応答 × Lambert 後方散乱で海底の帯を足し上げて合成。find_peaks + peak_subbin で検出。 (figure labels are in Japanese; the numbers are the same)
↑ 長さは 125 / 2271 / 21396 µs(170 倍の開き)。
↑ Δc = -40 m/s の深水漸近値は 44.83 度。
↑ 素子 96 本・λ/2 間隔。
↑ ○ = 在る。
↑ The animation ―― 主図(動画、640 × 360・30 fps・12 秒): 深さ 50 m の平らな海底を、船が 2 本の測線(間隔 101.4 m)で測る。水色は真の音線(水柱の音速差 -40 m/s の線形プロファイルで円弧に曲がる)、白い点と面は直下較正した等音速の処理が記録する海底で、色は「測った − 真の深さ」(高さの誤差だけ画面上 5 倍)。直下は +0.000 m、65 度は -2.697 m —— 平らな海底が外側だけ持ち上がる「スマイル」。最後に重なり帯を回り込むと、同じ海底を直下と最外ビームで測った 2.697 m の段差(TVU 0.820 m の 3.3 倍)が立っている。
py -3.11 examples/poc_multibeam_bathymetry.py
Source: examples/poc_multibeam_bathymetry.py
This run produced 19 figures in total - see them all
Ops used (notes): beamform_delay_sum · beamform_doa · color_bar · dem_slope · find_peaks · intensity · interp_scattered · peak_subbin · snell_angle · text_box
D10 / D50 / D90 from a synthetic scatter of particles, with merges (pulling large) and edge cuts (pulling small) counted separately. At an area fraction of 13.8 % the D50 error is +0.55 % — because 28 merges and 19 edge cuts happen to balance. Number- and area-weighting give D50 values of 26.9 and 44.7 µm (1.66x) from the same blobs.
↑ Particle Size Distribution From Images — Merging and Edge Cuts Pull Opposite Ways and Cancel ―― D10 / D50 / D90 from a synthetic scatter of particles, with merges (pulling large) and edge cuts (pulling small) counted separately. At an area fraction of 13.8 % the D50 error is +0.55 % — because 28 merges and 19 edge cuts happen to balance. Number- and area-weighting give D50 values of 26.9 and 44.7 µm (1.66x) from the same blobs.
↑ The measurement ―― 薄いところで 0 なのは正確だから。濃いところで 0 をまたぐのは融合と縁切れが釣り合っただけ。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ この回の図
↑ この回の図
py -3.11 examples/poc_particle_sizing.py
Source: examples/poc_particle_sizing.py
This run produced 7 figures in total - see them all
Ops used (notes): blob_features · blob_label · blob_overlay · blob_region · blob_select · circularity · distance_transform
Viewed through polarisers, a loaded transparent plastic shows its internal stress as fringes. Stress in a squeezed disc was recovered from polarisation images, including pixels where the fringe order exceeds 0.5 and the phase wraps. Recovery fails first at isotropic points where fringe contrast drops.
↑ Stress by Photoelasticity — Unwrapping Fails First at the Isotropic Point ―― The closed-form stress field of a diametrally loaded disc (4.2441 MPa at the centre, fringe order 2.380) turned into polariscope images by the Mueller-matrix (4-by-4 polarization-transfer matrix) ops and read back to stress. The op-built polariscope matches the textbook formula to 2.2e-16 across 125 cases. Phase wraps in the 84.2 % of pixels above fringe order 0.5, and unwrapping breaks first not where stress is highest but at the isotropic point, where modulation vanishes.
Terms: Mueller matrix — A real 4-by-4 matrix acting on a Stokes vector to describe how an optical element or sample changes polarization.
↑ The measurement ―― 左下 2 枚が「壊れる予報」。予報は当たるが、外せば直るとは限らない。 (figure labels are in Japanese; the numbers are the same)
↑ The animation ―― 動画(230 コマ、円板 φ50 mm を 361 画素で描画、半径 0.9R の外は描かない): 前半は荷重を 0 → 500 N へ上げる。暗視野(円偏光)の暗線は縞次数が整数の等値線で、荷重点から湧き出して中心へ寄る。中心の縞次数は荷重に比例して 2.38 まで増え(閉形式 h(σ1-σ2)/fσ)、右のグラフの中心の明るさ sin²(πN) が 0 に落ちるたびに暗線が中心を通過する(通過 2 回)。後半は荷重 500 N のまま、直交させた平面偏光子の対を 0 → 90 度回す。平面偏光の黒には 2 種類あり、回しても動かない縞は等色線(暗視野と同じ)、回すと動く黒い帯が等傾線 = 主応力の向きが偏光子と平行か直交する点で、中央の真値 θ の図で白く塗った点と重なる。偏光系は fullseye の mueller_element / mueller_apply(暗視野)と sin²(2(θ-β))·sin²(δ/2)(平面)。
py -3.11 examples/poc_photoelasticity.py
Source: examples/poc_photoelasticity.py
This run produced 3 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · mueller_apply · mueller_element · plot_series · text_box · ticks · unwrap_phase_2d
| Reading track irregularity with a chord (versine) — the distance from the mid-point to a chord drawn between two points, still the working method in track recording and in acceptance of rail re-profiling. The null baseline, reading the versine directly as height, ranges from 0.0 % to 200.0 % of the truth depending on wavelength: one and the same 10 m chord reads lambda=10 m at +100 %, lambda=1.5 m at +50 %, lambda=30 m at -50 %, and lambda=5.0 / 2.5 / 1.0 m at -100 %. Over-reading and under-reading happen at once, so no single correction factor repairs it. The blind spots are exact geometry: | H(lambda) | = | 1 - cos(pi L / lambda) | is exactly zero at lambda = L/(2n) and doubles at lambda = L/(2n+1). Across ten wavelengths and two chords, twenty cases, prediction and measurement differ by at most 0.00000, and a 0.600 mm undulation at lambda=5.00 m gives a versine whose largest absolute value over 200 m is 0.00000 mm. Sorting into third-octave wavelength bands does not remove it (the ratio spans 0.00041 to 2.000, a factor of 4924), and the operator’s total_power earned its place here: the band sum is only 0.519 of the sum over all FFT bins, and the missing 48 % is the 30 m irregularity sitting below f_min — invisible if only the bands are read. Dividing by | H | to invert diverges to 6.9e7 mm at the blind wavelengths, and regularising it makes three wavelengths quietly report ‘no irregularity’. Two chords (10 m and 6 m) recover eight of the ten wavelengths to within 0.1 %, but the shared blind spot is not a point: it is the comb lambda = 1.000/k. One prediction was wrong — the planted lambda=0.100 m also survived, and it turned out to be the k=10 tooth. The relative spacing of the comb equals lambda itself, so it thickens toward short wavelengths: the corrugation band 0.03-0.30 m alone holds 30 blind wavelengths. At the 0.25 m sampling of a recording car, a 30 mm corrugation reappears as a 0.750 m undulation of 0.0528 mm, nine times the 0.00584 mm floor of a control with the short waves switched off. An asymmetric chord (3.7 m and 6.3 m arms) removes the long-wave blind spots, but | H | = 0 requires a/lambda and b/lambda to be integers together, so the blind spots move to lambda = gcd(a, b)/k = 0.100 m: the chord built to measure corrugation is blind inside the corrugation band. |
| *↑ Measuring Rail with a Chord — At the Wavelengths Where the Transfer Function Is Zero, Any Amplitude Reads Zero ―― Reading track irregularity with a chord (versine) — the distance from the mid-point to a chord drawn between two points, still the working method in track recording and in acceptance of rail re-profiling. The null baseline, reading the versine directly as height, ranges from 0.0 % to 200.0 % of the truth depending on wavelength: one and the same 10 m chord reads lambda=10 m at +100 %, lambda=1.5 m at +50 %, lambda=30 m at -50 %, and lambda=5.0 / 2.5 / 1.0 m at -100 %. Over-reading and under-reading happen at once, so no single correction factor repairs it. The blind spots are exact geometry: | H(lambda) | = | 1 - cos(pi L / lambda) | is exactly zero at lambda = L/(2n) and doubles at lambda = L/(2n+1). Across ten wavelengths and two chords, twenty cases, prediction and measurement differ by at most 0.00000, and a 0.600 mm undulation at lambda=5.00 m gives a versine whose largest absolute value over 200 m is 0.00000 mm. Sorting into third-octave wavelength bands does not remove it (the ratio spans 0.00041 to 2.000, a factor of 4924), and the operator’s total_power earned its place here: the band sum is only 0.519 of the sum over all FFT bins, and the missing 48 % is the 30 m irregularity sitting below f_min — invisible if only the bands are read. Dividing by | H | to invert diverges to 6.9e7 mm at the blind wavelengths, and regularising it makes three wavelengths quietly report ‘no irregularity’. Two chords (10 m and 6 m) recover eight of the ten wavelengths to within 0.1 %, but the shared blind spot is not a point: it is the comb lambda = 1.000/k. One prediction was wrong — the planted lambda=0.100 m also survived, and it turned out to be the k=10 tooth. The relative spacing of the comb equals lambda itself, so it thickens toward short wavelengths: the corrugation band 0.03-0.30 m alone holds 30 blind wavelengths. At the 0.25 m sampling of a recording car, a 30 mm corrugation reappears as a 0.750 m undulation of 0.0528 mm, nine times the 0.00584 mm floor of a control with the short waves switched off. An asymmetric chord (3.7 m and 6.3 m arms) removes the long-wave blind spots, but | H | = 0 requires a/lambda and b/lambda to be integers together, so the blind spots move to lambda = gcd(a, b)/k = 0.100 m: the chord built to measure corrugation is blind inside the corrugation band.* |
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ 10 m 弦は λ=5.0 / 2.5 / 1.67 / 1.25 / 1.0 m で厳密に 0、λ=10 / 3.33 m で 2 倍。
↑ この回の図
↑ この回の図
↑ 0.750 m の周期がきれいに立つ。
py -3.11 examples/poc_rail_corrugation.py
Source: examples/poc_rail_corrugation.py
This run produced 11 figures in total - see them all
Ops used (notes): octave_bands · octave_spectrum
The real photograph famous for ‘a global threshold cannot work here, the lighting falls away across the frame’ (scikit-image coins). The background does slope, 0.427 to 0.161 down the rows and 0.331 to 0.059 across the columns — and yet a plain global Otsu with hole filling and an area floor of 150 lands exactly on the true 24. The truth is fixed by three independent paths agreeing (an area plateau spanning 50 to 800, a Hough circle search with the radii stated explicitly, and Sobel plus hole filling) and then checked one-to-one, each circle falling inside exactly one component. The margin, however, is 0.05: adding a little more of the same slope drops the count to 22. A correct answer is not evidence of a safe one. At +0.30 the median area moves by only -0.33 % while the worst single coin moves -27.26 %, and the drift correlates with row position at r = -0.91 — true area does not depend on where a coin sits, so that correlation is error, entire. Flattening with gray_tophat keeps the count but shrinks the median area to 0.29x, and the raw component count differs by 30 % between 4- and 8-connectivity.
↑ Counting and Measuring Real Coins — A Correct Answer Is Not a Safe One ―― The real photograph famous for ‘a global threshold cannot work here, the lighting falls away across the frame’ (scikit-image coins). The background does slope, 0.427 to 0.161 down the rows and 0.331 to 0.059 across the columns — and yet a plain global Otsu with hole filling and an area floor of 150 lands exactly on the true 24. The truth is fixed by three independent paths agreeing (an area plateau spanning 50 to 800, a Hough circle search with the radii stated explicitly, and Sobel plus hole filling) and then checked one-to-one, each circle falling inside exactly one component. The margin, however, is 0.05: adding a little more of the same slope drops the count to 22. A correct answer is not evidence of a safe one. At +0.30 the median area moves by only -0.33 % while the worst single coin moves -27.26 %, and the drift correlates with row position at r = -0.91 — true area does not depend on where a coin sits, so that correlation is error, entire. Flattening with gray_tophat keeps the count but shrinks the median area to 0.29x, and the raw component count differs by 30 % between 4- and 8-connectivity.
Terms: Otsu — A histogram thresholding method that chooses the threshold maximizing the variance between the two resulting classes.
↑ The measurement ―― 見た目はほとんど変わらないのに 2 枚落ちる。 (figure labels are in Japanese; the numbers are the same)
↑ 代表値で報告すると、壊れているのに壊れていないように見える。
↑ 生の個数は近傍の規約で 3 割違う。
py -3.11 examples/poc_real_coin_metrology.py
Source: examples/poc_real_coin_metrology.py
This run produced 4 figures in total - see them all
Ops used (notes): blob_count · blob_features · blob_label · canny · fill_holes · gray_tophat · hough_circle_trans · otsu · sobel_amp
An M6-like silhouette drawn from the ISO 68-1 basic triangle in closed form (1 px = 25 µm). An FFT of the binarised column widths reports half the true pitch, 20 px, because the two profiles are offset by P/2 and their sum is constant. Tilting the axis by 3 degrees splits the flank angles into 33.18 / 26.74 degrees: their half-sum 29.81 is the true flank angle and their half-difference 3.19 estimates the tilt. Pitch measured on one flank drifts at first order (+3.28 / -2.79 %) while the crest spacing drifts at second order (-0.12 %); undoing the tilt leaves P -0.009 % and d2 +0.033 %.
↑ Pitch, Flank Angle and Pitch Diameter From a Thread Silhouette — Tilt Shows Up With Opposite Signs on the Two Flanks ―― An M6-like silhouette drawn from the ISO 68-1 basic triangle in closed form (1 px = 25 µm). An FFT of the binarised column widths reports half the true pitch, 20 px, because the two profiles are offset by P/2 and their sum is constant. Tilting the axis by 3 degrees splits the flank angles into 33.18 / 26.74 degrees: their half-sum 29.81 is the true flank angle and their half-difference 3.19 estimates the tilt. Pitch measured on one flank drifts at first order (+3.28 / -2.79 %) while the crest spacing drifts at second order (-0.12 %); undoing the tilt leaves P -0.009 % and d2 +0.033 %.
↑ The measurement ―― 幅の系列は上下輪郭(P/2 ずれ)の和なので基本波が消える。 (figure labels are in Japanese; the numbers are the same)
↑ 片側のフランクだけで測ると 1 度あたり約 1 % 狂う。
↑ 半差が θ、半和が α。
↑ 生 = 水平 caliper 4 山、補正 = θ_est の向きの caliper 16 山(3 種の平均)。
↑ 測れなかった点は描かない。
py -3.11 examples/poc_screw_thread_metrology.py
Source: examples/poc_screw_thread_metrology.py
This run produced 9 figures in total - see them all
Ops used (notes): fit_line_contours · gen_measure_rectangle2 · hx_split_contours · measure_pos · threshold · threshold_sub_pix · xg_regress_contours
Stockpile inventory from a 3-D scan, for mining, aggregates and ports. The pile and the ground beneath it are written as separate formulas, and making the pile a sum of three cones at a 37 degree angle of repose closes the volume analytically at 3572.6089 m^3, matching a numerical integration at 0.025 m cells. The point is that the single number everyone quotes is fixed by a surface nobody measured: the ground under the pile. The first cliff is the assumed base, stated in closed form as dV = -A dh before measuring and matching to 0.0000 m^3 over seven offsets. The surprise is not the agreement but the size: 5 cm, which no survey would call an error, is 1.269 % of the inventory, or 72.5 t at a bulk density of 1.6 t/m^3 — three truckloads. The second cliff is occlusion. A cone is a ruled surface, so the visible fraction is arccos((H - h_s)/(D tan phi))/pi, matching to within 0.0201 over six geometries, and that residual halves with the cell size (0.0339, 0.0171, 0.0120 at 1.2, 0.6 and 0.4 m), which identifies it as discretisation rather than a wrong model. One prediction was wrong: interpolating across the occluded side was expected to under-estimate the volume, since a cone is concave and a chord passes below it, but the measurement is +17.20 %, an over-estimate. The far side is hidden all the way to the toe, so the triangulation connects the ridge not to the pile but to the ground beyond it, and a chord thrown 30 m past the ridge has a slope of 0.37 m/m that passes above the true 0.75 m/m slope. Then a cancellation trap: with the same single scan, using the true ground gives +17.20 % while the field practice of a horizontal base at the mean perimeter height gives +0.51 %. Nothing improved — the perimeter was lifted by the same interpolation, by +0.728 m, and the subtraction hides it; using only the perimeter points that were actually seen returns +12.05 %. A contaminated ruler applied to a contaminated object makes the error look like it went away. The same shape recurs with a berm of spoil at the toe: the outlier-resistant RANSAC fit reads worse (+2.48 %) than total least squares (+0.61 %), but RANSAC correctly rejected the berm and returned to the no-berm answer of +1.91 %, while TLS looked good because the berm’s lift happened to cancel the bias from the undulating ground. Averaging the perimeter cancels the ground’s tilt whenever the footprint is roughly symmetric — the flat control reads +0.01 % — so the remaining +1.79 % is entirely undulation, and fitting a tilted plane makes it worse (+1.91 %): more freedom does not mean less bias. The rulers disagree on the winner, with the horizontal base slightly better on volume and the fitted plane six times better on the centroid (0.07 m against 0.42 m), which is the quantity a loading plan uses. And the two errors do not add: -0.70 % expected against +0.51 % measured, because the occlusion fill moves the perimeter that fixes the base. Finally, a real bug was found and fixed: dem_viewshed reported every cell above the observer’s eye as not visible — the apex of a cone on open flat ground read 0.0, none of the 1541 cells above eye level were visible, and the occluded fraction came out 0.8863 against a closed-form 0.5710. Line-of-sight samples rounded onto the target cell itself, occluding it against its own height; skipping those samples restores 1.0 at the apex and 0.5900 for the occluded fraction. The existing tests missed it because they only checked flat ground and what lies behind a wall, never whether the wall itself is visible.
↑ Stockpile Inventory — The Answer Is Fixed by the Ground Nobody Measured ―― Stockpile inventory from a 3-D scan, for mining, aggregates and ports. The pile and the ground beneath it are written as separate formulas, and making the pile a sum of three cones at a 37 degree angle of repose closes the volume analytically at 3572.6089 m^3, matching a numerical integration at 0.025 m cells. The point is that the single number everyone quotes is fixed by a surface nobody measured: the ground under the pile. The first cliff is the assumed base, stated in closed form as dV = -A dh before measuring and matching to 0.0000 m^3 over seven offsets. The surprise is not the agreement but the size: 5 cm, which no survey would call an error, is 1.269 % of the inventory, or 72.5 t at a bulk density of 1.6 t/m^3 — three truckloads. The second cliff is occlusion. A cone is a ruled surface, so the visible fraction is arccos((H - h_s)/(D tan phi))/pi, matching to within 0.0201 over six geometries, and that residual halves with the cell size (0.0339, 0.0171, 0.0120 at 1.2, 0.6 and 0.4 m), which identifies it as discretisation rather than a wrong model. One prediction was wrong: interpolating across the occluded side was expected to under-estimate the volume, since a cone is concave and a chord passes below it, but the measurement is +17.20 %, an over-estimate. The far side is hidden all the way to the toe, so the triangulation connects the ridge not to the pile but to the ground beyond it, and a chord thrown 30 m past the ridge has a slope of 0.37 m/m that passes above the true 0.75 m/m slope. Then a cancellation trap: with the same single scan, using the true ground gives +17.20 % while the field practice of a horizontal base at the mean perimeter height gives +0.51 %. Nothing improved — the perimeter was lifted by the same interpolation, by +0.728 m, and the subtraction hides it; using only the perimeter points that were actually seen returns +12.05 %. A contaminated ruler applied to a contaminated object makes the error look like it went away. The same shape recurs with a berm of spoil at the toe: the outlier-resistant RANSAC fit reads worse (+2.48 %) than total least squares (+0.61 %), but RANSAC correctly rejected the berm and returned to the no-berm answer of +1.91 %, while TLS looked good because the berm’s lift happened to cancel the bias from the undulating ground. Averaging the perimeter cancels the ground’s tilt whenever the footprint is roughly symmetric — the flat control reads +0.01 % — so the remaining +1.79 % is entirely undulation, and fitting a tilted plane makes it worse (+1.91 %): more freedom does not mean less bias. The rulers disagree on the winner, with the horizontal base slightly better on volume and the fitted plane six times better on the centroid (0.07 m against 0.42 m), which is the quantity a loading plan uses. And the two errors do not add: -0.70 % expected against +0.51 % measured, because the occlusion fill moves the perimeter that fixes the base. Finally, a real bug was found and fixed: dem_viewshed reported every cell above the observer’s eye as not visible — the apex of a cone on open flat ground read 0.0, none of the 1541 cells above eye level were visible, and the occluded fraction came out 0.8863 against a closed-form 0.5710. Line-of-sight samples rounded onto the target cell itself, occluding it against its own height; skipping those samples restores 1.0 at the apex and 0.5900 for the occluded fraction. The existing tests missed it because they only checked flat ground and what lies behind a wall, never whether the wall itself is visible.
↑ The measurement ―― A = 906.5 m^2。閉形式は測る前に印字してある。 (figure labels are in Japanese; the numbers are the same)
↑ 2 本は重なる。
↑ 可視率 = arccos((H-h_s)/(D tanφ))/π。
↑ 3 か所で遮蔽は 1.4 % まで落ちるが、うねり由来の +1.8 % は何か所測っても消えない(底面は誰も測っていない)。
↑ 対照群(平ら/全周)で 2 つを切り分けてから、両方入れる。
↑ The animation ―― 主図(動画、640 × 360・30 fps・12 秒): うねりのある地面に置いた山の周りを一周しながら、走査位置を 1 → 2 → 3 か所と増やす。描いているのは補間で埋めた DSM(在庫計算が信じている面)で、色は「補間 − 真の面」(最大 5.23 m)。見えなかった割合は 0.660 → 0.315 → 0.014。在庫量の誤差は真の底面で +17.20 % → +3.05 % → +0.05 %、外周平均の水平底面で +0.51 % → +2.41 % → +1.83 % —— 3 か所で遮蔽は塞がるが、うねり由来の偏りは残る。高さは実寸。
py -3.11 examples/poc_stockpile_volume.py
Source: examples/poc_stockpile_volume.py
This run produced 7 figures in total - see them all
Ops used (notes): color_bar · dem_hillshade · dem_slope · dem_viewshed · interp_scattered · moment_axes · text_box
One hour of creep in 25 frames, with strain history from accumulating adjacent-frame displacements versus comparing each frame directly with the reference. At the end, cumulative errs by 61 µε and direct by 1878 µε — cumulative wins 31x and the textbook crossover in time never appears (it lives on the noise axis instead). Thinning from 24 to 4 steps worsens cumulative from -60 to -606 µε; what matters is the deformation per step, not the number of steps.
↑ Strain History in a Creep Test — Cumulative or Direct? ―― One hour of creep in 25 frames, with strain history from accumulating adjacent-frame displacements versus comparing each frame directly with the reference. At the end, cumulative errs by 61 µε and direct by 1878 µε — cumulative wins 31x and the textbook crossover in time never appears (it lives on the noise axis instead). Thinning from 24 to 4 steps worsens cumulative from -60 to -606 µε; what matters is the deformation per step, not the number of steps.
↑ The measurement ―― 直接の偏りだけが伸びる。累積は偏りも散らばりも頭打ちで、しかも散らばりより偏りのほうが大きい ——ランダムウォークではない。 (figure labels are in Japanese; the numbers are the same)
↑ 因果フィルタの偏りは遅れ (w-1)/2 の閉形式にほぼ乗る。
↑ 予測 = w=1 の偏り + 閉形式(中央はなまり、因果は遅れ)。
↑ The animation ―― 動画(720 × 458、8 fps、127 コマ): クリープ試験を 25 コマ撮る。左はその時刻のスペックル像に、t=0 との直接 PIV の変位を 3 倍の矢印で重ねたもの(中心から外へ伸びる)。右上は真ひずみ(白、閉形式)と測った値(青 = 隣のコマどうしの増分を足す累積、朱 = いつも t=0 と比べる直接)、右下はその誤差。直接の誤差だけが変形とともに伸び、終端で 累積 -49 µε / 直接 -1888 µε(雑音の実現 1 通り、第 2 節と同じ種)。
py -3.11 examples/poc_strain_history.py
Source: examples/poc_strain_history.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · data_to_pixel · moving_average_window · piv_cross_correlate · piv_error_stats · piv_multipass · piv_sample_at_windows · piv_synth_pair · poly_fit · text_box · ticks
A prescribed-PSD synthetic surface is measured. Raw rms overstates Sq 20 times; plane removal still leaves 1.8 times. At 8 micrometre sampling Sa is -3.5% and Sz -19.8%. Sq changes little only for this setup; decimation does not generally preserve the second moment. Sz did not plateau over tested areas. Full-length peak-to-valley is Rt, not standardised Rz.
↑ How Far Sa / Sq / Sz Survive Sampling and Cutoff ―― A prescribed-PSD synthetic surface is measured. Raw rms overstates Sq 20 times; plane removal still leaves 1.8 times. At 8 micrometre sampling Sa is -3.5% and Sz -19.8%. Sq changes little only for this setup; decimation does not generally preserve the second moment. Sz did not plateau over tested areas. Full-length peak-to-valley is Rt, not standardised Rz.
↑ The measurement ―― dx=8 µm では Sa が ±5 % 合格で Sz が不合格。同じデータでも見るパラメータで結論が反転する。 (figure labels are in Japanese; the numbers are the same)
↑ 頭打ちにならないので『真の Sz』は存在しない。
↑ 左は加工目(λ=32µm)を落として過小、右はうねり(λ=256µm)が漏れて過大。
py -3.11 examples/poc_surface_roughness.py
Source: examples/poc_surface_roughness.py
This run produced 4 figures in total - see them all
Ops used (notes): profile_params · surface_filter · surface_form_remove · surface_params · surface_psd · surface_synth_psd
| The second exhibit of the physics-simulation × Fullseye series (‘home-made traps have a ceiling: bring in the truth, the gate or the subject from outside’). Two closed-form systems come from outside. Elastic contact: Hertz (Johnson, Contact Mechanics, CUP 1985: a³ = 3FR/(4E), δ = a²/R, p(r) = p0√(1 − r²/a²), half-space surface displacement δ − r²/(2R) inside and eq. 3.42a outside, value δ/2 and slope −a/R both continuous at r = a). Optics: Woodham’s 1980 photometric stereo N = L⁻¹I and the Frankot–Chellappa 1988 normal integration (the principle of reading normals from one image of an elastic membrane lit by three coloured directional lights is Johnson & Adelson, CVPR 2009). Three things are our own: the closed-form radial slope of the outer solution, dh/dr = (2/πR)[r arcsin(a/r) − a√(1 − a²/r²)] (derived; matches numerical differentiation to 5e-9), an inversion that fits this one-parameter Hertz slope model to the radial slope profile of the recovered normal field (it never integrates heights, so FFT amplitude damping, the finite window and the unknown offset do not enter), and a window-truncation correction that adds the Boussinesq far-field tail ū_z ≈ F/(πEr) as r_max·s̄(r_max) to the 1-D slope integral for δ. The subjects are Fullseye’s existing ops (photometric_stereo / integrate_normals / surface_normals / render_lambertian, measure.fit_circle). New module tacsim, 14 ops. Figures: a 1:1 crop around the indentation (62.5 µm/px, a = 0.88 mm = 14 px), synthetic images and recovered heights for a sphere, a cylinder, a straight edge and an ‘F’ stamp, a GIF ramping the load 0.005 → 0.12 N (left the synthetic image, right the Hertz a–F curve filling with points), the recovered-vs-truth cross-section, the slope profile against the Hertz model, a–F with both readouts, and where it breaks (mis-calibration, noise). 17 gates: limits of the combined modulus, Hertz identities (1e-9), pressure integrals equal F and F/L (0.1 %), inner/outer continuity of the surface displacement and the slope derivation, Frankot–Chellappa round trip 0.11 µm (δ = 261 µm), Woodham exactness (median 0.0001°), the fit route a 0.05–0.26 % and F 0.14–0.79 % (0.02–0.12 N), the δ route δ 0.28–0.84 % and F 0.43–1.26 %, the model-free ring −8.2 to −4.9 % (within 1 pt of the resolution prediction −0.7 px/a = −7.8 to −4.3 %), a ∝ F^{1/3} (exponent 0.3335), window truncation deficit 4.37 % (closed form ū_z(r_max)/δ = 4.7 %), not subtracting the ambient 0.03 reads δ 3.17 % low (predicted ambient/sin 55° = 3.7 %), a 15° elevation mis-calibration spoils only the slopes (0.00° → 5.69°, flat gel 0.05°), pixel noise σ = 0.03 gives 2.7° on normals yet F within 0.5 %, the four shapes round-trip to 0.4–0.6 µm RMS (the stamp 10.8 µm from attached shadows on its 60° walls), the cylinder’s geometric half-width √(2Rd − d²) = 1.308 mm (measured 1.312), and tac_contact_mask has recall 0.92 but 1.78× the area. Honestly: small-strain Hertz (δ/R ≈ 0.09), Lambertian, no shadows or speculars, a semi-infinite membrane, no viscoelasticity or markers. The model-free ring sits 5 % inside at a = 14 px (the fit route is primary, the ring is the second implementation). Traps: a thresholded | ∇h | band sits 9 % inside because the cusp is asymmetric, FFT integration damps a deep local dimple by 13 %, and the ambient term tilts normals 3.7 % toward flat (the subtraction that a real sensor’s reference frame provides is needed). Gates alone 0.7 s, with figures 12 s. |
| ↑ A Vision-Based Tactile Sensor (Elastic Membrane + Camera), Synthesised and Inverted — Hertz Contact and Photometric Stereo in Closed Form as the Gate, Force Read from a Pressed Sphere ―― The second exhibit of the physics-simulation × Fullseye series (‘home-made traps have a ceiling: bring in the truth, the gate or the subject from outside’). Two closed-form systems come from outside. Elastic contact: Hertz (Johnson, Contact Mechanics, CUP 1985: a³ = 3FR/(4E), δ = a²/R, p(r) = p0√(1 − r²/a²), half-space surface displacement δ − r²/(2R) inside and eq. 3.42a outside, value δ/2 and slope −a/R both continuous at r = a). Optics: Woodham’s 1980 photometric stereo N = L⁻¹I and the Frankot–Chellappa 1988 normal integration (the principle of reading normals from one image of an elastic membrane lit by three coloured directional lights is Johnson & Adelson, CVPR 2009). Three things are our own: the closed-form radial slope of the outer solution, dh/dr = (2/πR)[r arcsin(a/r) − a√(1 − a²/r²)] (derived; matches numerical differentiation to 5e-9), an inversion that fits this one-parameter Hertz slope model to the radial slope profile of the recovered normal field (it never integrates heights, so FFT amplitude damping, the finite window and the unknown offset do not enter), and a window-truncation correction that adds the Boussinesq far-field tail ū_z ≈ F/(πE*r) as r_max·s̄(r_max) to the 1-D slope integral for δ. The subjects are Fullseye’s existing ops (photometric_stereo / integrate_normals / surface_normals / render_lambertian, measure.fit_circle). New module tacsim, 14 ops. Figures: a 1:1 crop around the indentation (62.5 µm/px, a = 0.88 mm = 14 px), synthetic images and recovered heights for a sphere, a cylinder, a straight edge and an ‘F’ stamp, a GIF ramping the load 0.005 → 0.12 N (left the synthetic image, right the Hertz a–F curve filling with points), the recovered-vs-truth cross-section, the slope profile against the Hertz model, a–F with both readouts, and where it breaks (mis-calibration, noise). 17 gates: limits of the combined modulus, Hertz identities (1e-9), pressure integrals equal F and F/L (0.1 %), inner/outer continuity of the surface displacement and the slope derivation, Frankot–Chellappa round trip 0.11 µm (δ = 261 µm), Woodham exactness (median 0.0001°), the fit route a 0.05–0.26 % and F 0.14–0.79 % (0.02–0.12 N), the δ route δ 0.28–0.84 % and F 0.43–1.26 %, the model-free ring −8.2 to −4.9 % (within 1 pt of the resolution prediction −0.7 px/a = −7.8 to −4.3 %), a ∝ F^{1/3} (exponent 0.3335), window truncation deficit 4.37 % (closed form ū_z(r_max)/δ = 4.7 %), not subtracting the ambient 0.03 reads δ 3.17 % low (predicted ambient/sin 55° = 3.7 %), a 15° elevation mis-calibration spoils only the slopes (0.00° → 5.69°, flat gel 0.05°), pixel noise σ = 0.03 gives 2.7° on normals yet F within 0.5 %, the four shapes round-trip to 0.4–0.6 µm RMS (the stamp 10.8 µm from attached shadows on its 60° walls), the cylinder’s geometric half-width √(2Rd − d²) = 1.308 mm (measured 1.312), and tac_contact_mask has recall 0.92 but 1.78× the area. Honestly: small-strain Hertz (δ/R ≈ 0.09), Lambertian, no shadows or speculars, a semi-infinite membrane, no viscoelasticity or markers. The model-free ring sits 5 % inside at a = 14 px (the fit route is primary, the ring is the second implementation). Traps: a thresholded | ∇h | band sits 9 % inside because the cusp is asymmetric, FFT integration damps a deep local dimple by 13 %, and the ambient term tilts normals 3.7 % toward flat (the subtraction that a real sensor’s reference frame provides is needed). Gates alone 0.7 s, with figures 12 s.* |
↑ The measurement ―― 球・円柱・直線エッジ・F 字スタンプを 0.3 mm 押し込んだ膜(幾何学的な追従、弾性の裾なし、46.9 µm/px、中央 ±1.9 mm を 3 倍)。上 = 3 色照明の合成像、下 = フォトメトリックステレオ + Frankot-Chellappa で復元した高さ(接触域の RMS を併記)。 (figure labels are in Japanese; the numbers are the same)
↑ 中央行の断面。
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_tacsim_elastic_membrane/05_tacsim_slope_profile_fit.png)
↑ 法線場から取った半径方向スロープの方位平均(点)と、1 パラメータ a で当てた Hertz のスロープ模型(破線: 内側 r/R、外側 (2/πR)[r arcsin(a/r) − a√(1−a²/r²)])。
↑ 破線 = Hertz の閉形式。
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_tacsim_elastic_membrane/07_tacsim_failure_modes.png)
↑ 壊れる場所: 法線の角誤差 [deg]。
↑ The animation ―― 荷重を 0.005 → 0.12 N に上げる(12 コマ)。左 = 圧痕まわり ±2.0 mm の合成像(62.5 µm/px を 4 倍)、右 = Hertz の a–F 曲線(破線 = 閉形式)に、各コマの像から当てはめ経路で読んだ a が点として増えていく。
py -3.11 examples/poc_tacsim_elastic_membrane.py
Source: examples/poc_tacsim_elastic_membrane.py
This run produced 7 figures in total - see them all
Ops used (notes): combined_modulus · contact_radius_fit · contact_radius_ring · hertz_cylinder · hertz_force · hertz_pressure · hertz_sphere · hertz_surface_uz · integrate_normals · membrane_delta_from_normals · membrane_indent_shape · membrane_indent_sphere · membrane_lights · membrane_recover · membrane_render_rgb · photometric_stereo · tac_contact_mask
| The second exhibit of the second physics-simulation × Fullseye round (the first read force from a normal indentation). Two things come from outside. Closed forms (Johnson, Contact Mechanics, CUP 1985): the Cattaneo 1938 / Mindlin 1949 partial-slip solution (§7.2) — a sphere pressed with normal P and then loaded tangentially with Q < μP has a stick circle c/a = (1 − Q/μP)^{1/3}, a shear traction q = q′ − q″ (the difference of two Hertz-shaped tractions, pinned at the Coulomb limit μp(r) in the slip annulus), a rigid-sphere tangential displacement δx = 3μP(2−ν)/(16Ga)[1 − (1−Q/μP)^{2/3}], an initial tangential stiffness kt = 8Ga/(2−ν), and a uniform surface displacement inside the stick circle; the inner solution for one Hertz-shaped tangential traction (eq. 3.91); the radial surface displacement under normal load (eq. 3.41b); and the Cerruti half-space solution for a tangential point load (eq. 3.22). Finite-element nodal displacements of a finite-thickness dome-shaped gel (shipped with the Robo-Touch/Taxim repository, MIT; used only when FULLSEYE_TAXIM_DATA is set, never copied into the repo). Published qualitative facts (Yuan, Dong & Adelson, Sensors 2017): slip starts at the periphery and the entropy of the marker-displacement histogram rises under partial slip. Three things are our own: outside the contact circle and in the slip annulus there is no closed form, so the Cerruti kernel is discretised as pixel averages (analytic centre pixel, 4h ln(1+√2)) and convolved by FFT (ūx 0.017 % and ūy 0.03 % against eq. 3.91 inside the circle, stick-core uniformity std 0.006 %); a similarity-law inverse model g(x) − (c/a)²g(x·a/c) that yields the field for any stick radius from one convolution (c/a to 0.001 on the true displacements); and marker images drawn by moving the centres before rendering (no interpolation). The subjects are Fullseye’s existing ops: blob2d.blob_label / blob_features (centroids), pivops.piv_cross_correlate (window correlation, second implementation), the sub-pixel extrema of backends_subpix, backends_tactile.tac_shear_field (a separate subject), measure.fit_circle, and tacsim’s Hertz table and membrane rendering. New module tacslip, 20 ops. The dimensions were chosen to kill the resolution trap first: R = 6 mm, P = 0.5 N, E 0.2 MPa, ν 0.48 → a = 2.05 mm = 32.9 px (62.5 µm/px), markers at 0.5 mm = 8 px (radius 2.5 px) giving 53 inside the contact circle, full-slip δx = 514 µm = 8.2 px; μ = 0.5 is an assumption (the value that reproduces the plan’s anchor of 70.5 µm). Figures: a triptych of reference / loaded / tracked vectors (uniform motion inside the stick circle, lag in the slip annulus, a 1/r tail outside), a GIF ramping the tangential force 0 → μP (the closed-form stick circle in green and the fitted c in red coincide, and the core vanishes at full slip), the two terms of q(r), δx–Q (closed-form line and stick-core medians), entropy vs Q/μP, finite elements vs half-space (decay of r·u and the angular form of dx(θ)), and where it breaks (lattice aliasing, density, noise). 20 gates (the two finite-element gates when the data is present; gates alone 2.8 s, with figures 19 s): ∫q dA = Q (6e-7), the closed-form c/a and the full-slip flag, dδx/dQ(0) = 1/kt (1e-6) and the 70.6 µm anchor, ūr(a)/δ = 2(1−2ν)/(3π(1−ν)) = 1.6 % (derived), the Cerruti convolution against eq. 3.91, stick-core uniformity, the far field 1/r (1.016 at 3a), the centroid round trip (iterated Gaussian-weighted 0.003 px, binary 0.16 px, lattice-wide common bias 0.010 → 0.004 px), markers move at most 0.187 px under the normal load alone = the maximum of | ūr | (at r = 0.93a, 1.022 × ūr(a)), tracking RMS 0.007–0.010 px (Q/μP 0.25–0.9, 961/961 matched), PIV reads a 5 px shift of an 8 px lattice as −3.000 px (4.999 on a jittered lattice) and agrees to 0.03 px in the stick core once the search is capped at ±3.8 px, the inversion’s Q/μP error 0.026 → 0.007, c/a 0.011, Q 0.4–0.9 %, μ 10.2 → 1.2 % (μ and Q separate once G, ν and a are known, but the error in μ is the error in c/a amplified by 2c/(1−c²) = 10.4 → 1.2), the model-free stick radius 2.3–4.7 px (= the marker pitch), c/a 0.000 and a 42 % spread of the core at full slip, exponent 0.341 (closed form 1/3), entropy 0.078 → 0.677 non-decreasing, tac_shear_field does not separate stick from slip (core 0.043, annulus 0.039), Q/μP within 0.03 even at a 16 px pitch (13 markers inside) and tracking RMS 0.031 px at pixel noise σ 0.05, and for the finite elements r·dz falls to half of the 1/r law at r½ = 2.27 mm (0.88 at 1 mm, 0.57 at 2 mm, 0.32 at 3 mm) while dx(θ) at r = 1.5–2 mm follows Cerruti’s A + B cos²θ (R² 0.78–0.80) with ν 0.49–0.51 and exceeds the half-space bound of 2 at r = 3 mm (2.4). Honestly: half-space, small strain, rigid sphere, Coulomb friction, quasi-static (no time evolution of incipient slip, no viscoelasticity; the shear strain near the edge approaches 0.25 at full slip, outside linear elasticity), μ is poorly determined at low Q (10 % at Q/μP 0.25), the finite-element data is a point-like load (contact smaller than the 0.16 mm node spacing) with unknown force so the stick circle cannot be tested and only shapes are compared (even the normal-load case carries dy/dz = 0.25 of asymmetry), correlation and nearest-neighbour matching cannot in principle measure | u | ≥ pitch/2 (the matching relies on the image border being far field), and 53 markers quantise the entropy. Traps: correlation on a regular lattice aliases at the lattice period (a 64→32 multipass jumps to ±8/±16 in its first pass), the 6.4 px core displacement lands 1.6 px from the neighbouring reference position so even nearest-neighbour seeds jump (hence growing the matching inward from the border), the centroid of a 2 px disc carries ±0.03 px of pixel-locking that becomes a lattice-wide common mode and shifts c/a by 0.02 (a 2.5 px radius plus iterated Gaussian weights cut it to a third; a free rigid-shift term is degenerate with the 1/r tail and makes it worse), the maximum of ūr sits inside the edge at 0.93a, neighbouring marker fringes merge in the slip annulus as the spacing shrinks from 8 to 6 px, and a model that interpolates fields on a c/a grid is off by 0.01. δx_full ≈ marker pitch is the worst pairing — a real sensor wants pitch > 2·δx_full or an aperiodic layout. |
| *↑ Reading the Shear Field, Stick/Slip and Tangential Force from a Tactile Sensor’s Marker Array — Cattaneo–Mindlin in Closed Form and Finite-Element Nodal Displacements as the Gate ―― The second exhibit of the second physics-simulation × Fullseye round (the first read force from a normal indentation). Two things come from outside. Closed forms (Johnson, Contact Mechanics, CUP 1985): the Cattaneo 1938 / Mindlin 1949 partial-slip solution (§7.2) — a sphere pressed with normal P and then loaded tangentially with Q < μP has a stick circle c/a = (1 − Q/μP)^{1/3}, a shear traction q = q′ − q″ (the difference of two Hertz-shaped tractions, pinned at the Coulomb limit μp(r) in the slip annulus), a rigid-sphere tangential displacement δx = 3μP(2−ν)/(16Ga)[1 − (1−Q/μP)^{2/3}], an initial tangential stiffness kt = 8Ga/(2−ν), and a uniform surface displacement inside the stick circle; the inner solution for one Hertz-shaped tangential traction (eq. 3.91); the radial surface displacement under normal load (eq. 3.41b); and the Cerruti half-space solution for a tangential point load (eq. 3.22). Finite-element nodal displacements of a finite-thickness dome-shaped gel (shipped with the Robo-Touch/Taxim repository, MIT; used only when FULLSEYE_TAXIM_DATA is set, never copied into the repo). Published qualitative facts (Yuan, Dong & Adelson, Sensors 2017): slip starts at the periphery and the entropy of the marker-displacement histogram rises under partial slip. Three things are our own: outside the contact circle and in the slip annulus there is no closed form, so the Cerruti kernel is discretised as pixel averages (analytic centre pixel, 4h ln(1+√2)) and convolved by FFT (ūx 0.017 % and ūy 0.03 % against eq. 3.91 inside the circle, stick-core uniformity std 0.006 %); a similarity-law inverse model g(x) − (c/a)²g(x·a/c) that yields the field for any stick radius from one convolution (c/a to 0.001 on the true displacements); and marker images drawn by moving the centres before rendering (no interpolation). The subjects are Fullseye’s existing ops: blob2d.blob_label / blob_features (centroids), pivops.piv_cross_correlate (window correlation, second implementation), the sub-pixel extrema of backends_subpix, backends_tactile.tac_shear_field (a separate subject), measure.fit_circle, and tacsim’s Hertz table and membrane rendering. New module tacslip, 20 ops. The dimensions were chosen to kill the resolution trap first: R = 6 mm, P = 0.5 N, E 0.2 MPa, ν 0.48 → a = 2.05 mm = 32.9 px (62.5 µm/px), markers at 0.5 mm = 8 px (radius 2.5 px) giving 53 inside the contact circle, full-slip δx = 514 µm = 8.2 px; μ = 0.5 is an assumption (the value that reproduces the plan’s anchor of 70.5 µm). Figures: a triptych of reference / loaded / tracked vectors (uniform motion inside the stick circle, lag in the slip annulus, a 1/r tail outside), a GIF ramping the tangential force 0 → μP (the closed-form stick circle in green and the fitted c in red coincide, and the core vanishes at full slip), the two terms of q(r), δx–Q (closed-form line and stick-core medians), entropy vs Q/μP, finite elements vs half-space (decay of r·u and the angular form of dx(θ)), and where it breaks (lattice aliasing, density, noise). 20 gates (the two finite-element gates when the data is present; gates alone 2.8 s, with figures 19 s): ∫q dA = Q (6e-7), the closed-form c/a and the full-slip flag, dδx/dQ(0) = 1/kt (1e-6) and the 70.6 µm anchor, ūr(a)/δ = 2(1−2ν)/(3π(1−ν)) = 1.6 % (derived), the Cerruti convolution against eq. 3.91, stick-core uniformity, the far field 1/r (1.016 at 3a), the centroid round trip (iterated Gaussian-weighted 0.003 px, binary 0.16 px, lattice-wide common bias 0.010 → 0.004 px), markers move at most 0.187 px under the normal load alone = the maximum of | ūr | (at r = 0.93a, 1.022 × ūr(a)), tracking RMS 0.007–0.010 px (Q/μP 0.25–0.9, 961/961 matched), PIV reads a 5 px shift of an 8 px lattice as −3.000 px (4.999 on a jittered lattice) and agrees to 0.03 px in the stick core once the search is capped at ±3.8 px, the inversion’s Q/μP error 0.026 → 0.007, c/a 0.011, Q 0.4–0.9 %, μ 10.2 → 1.2 % (μ and Q separate once G, ν and a are known, but the error in μ is the error in c/a amplified by 2c/(1−c²) = 10.4 → 1.2), the model-free stick radius 2.3–4.7 px (= the marker pitch), c/a 0.000 and a 42 % spread of the core at full slip, exponent 0.341 (closed form 1/3), entropy 0.078 → 0.677 non-decreasing, tac_shear_field does not separate stick from slip (core 0.043, annulus 0.039), Q/μP within 0.03 even at a 16 px pitch (13 markers inside) and tracking RMS 0.031 px at pixel noise σ 0.05, and for the finite elements r·dz falls to half of the 1/r law at r½ = 2.27 mm (0.88 at 1 mm, 0.57 at 2 mm, 0.32 at 3 mm) while dx(θ) at r = 1.5–2 mm follows Cerruti’s A + B cos²θ (R² 0.78–0.80) with ν 0.49–0.51 and exceeds the half-space bound of 2 at r = 3 mm (2.4). Honestly: half-space, small strain, rigid sphere, Coulomb friction, quasi-static (no time evolution of incipient slip, no viscoelasticity; the shear strain near the edge approaches 0.25 at full slip, outside linear elasticity), μ is poorly determined at low Q (10 % at Q/μP 0.25), the finite-element data is a point-like load (contact smaller than the 0.16 mm node spacing) with unknown force so the stick circle cannot be tested and only shapes are compared (even the normal-load case carries dy/dz = 0.25 of asymmetry), correlation and nearest-neighbour matching cannot in principle measure | u | ≥ pitch/2 (the matching relies on the image border being far field), and 53 markers quantise the entropy. Traps: correlation on a regular lattice aliases at the lattice period (a 64→32 multipass jumps to ±8/±16 in its first pass), the 6.4 px core displacement lands 1.6 px from the neighbouring reference position so even nearest-neighbour seeds jump (hence growing the matching inward from the border), the centroid of a 2 px disc carries ±0.03 px of pixel-locking that becomes a lattice-wide common mode and shifts c/a by 0.02 (a 2.5 px radius plus iterated Gaussian weights cut it to a third; a free rigid-shift term is degenerate with the 1/r tail and makes it worse), the maximum of ūr sits inside the edge at 0.93a, neighbouring marker fringes merge in the slip annulus as the spacing shrinks from 8 to 6 px, and a model that interpolates fields on a c/a grid is off by 0.01. δx_full ≈ marker pitch is the worst pairing — a real sensor wants pitch > 2·δx_full or an aperiodic layout.* |
↑ The measurement ―― 接線力を 0 → μP に上げる(12 コマ)。左 = マーカー像 + 追跡ベクトル(4 倍)、緑 = 閉形式の固着円 c = a(1 − Q/μP)^{1/3}、赤 = 画像から当てた c、白 = 接触円 a。右 = c/a の閉形式(破線)に各コマの推定点が増える。Q = μP で核が消え全滑り。 (figure labels are in Japanese; the numbers are the same)
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_tacsim_marker_shear/03_tacslip_traction_q_r.png)
↑ 接線トラクション q(r) = μp0[√(1−r²/a²) − (c/a)√(1−r²/c²)]。
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_tacsim_marker_shear/04_tacslip_delta_x_vs_Q.png)
↑ 剛体球の接線変位 δx = 3μP(2−ν)/(16Ga)[1 − (1−Q/μP)^{2/3}] (破線)と初期剛性 kt = 8Ga/(2−ν) の直線(点線)。
↑ 接触円内のマーカー変位の大きさのヒストグラム(16 ビン)のエントロピー。
↑ 第 2 真値: 有限要素の節点変位(ドーム状の有限厚ゲル、Robo-Touch/Taxim リポジトリ同梱、MIT)と半空間解。
py -3.11 examples/poc_tacsim_marker_shear.py
Source: examples/poc_tacsim_marker_shear.py
This run produced 7 figures in total - see them all
Ops used (notes): blob_features · cerruti_kernel · cerruti_surface_displacement · combined_modulus · displace_markers · fem_nodes_load · fem_vs_halfspace · hertz_pressure · hertz_sphere · hertz_surface_ur · hertzian_tangential_inner · marker_detect · marker_image · marker_track · membrane_indent_sphere · membrane_lights · membrane_markers · membrane_render_markers · membrane_render_rgb · membrane_shear_field · mindlin_fit · mindlin_model · mindlin_partial_slip · mindlin_traction … (+5)
| The third exhibit of the second physics-simulation × Fullseye round (the first read force from a normal indentation, the second shear and stick/slip from a marker array). The re-implemented method is Fuchioka & Hamaya, ICRA 2024 (arXiv 2404.15626): learning-free and optics-free, it treats the divergence ∇·v of the marker displacement field as a ‘charge’ (the normal-force distribution, by analogy with Gauss’s law), takes the dipole p = (1/N) Σ r_i (∇·v)_i about the midpoint of the positive and negative charge centroids, and reads the tilt torque orthogonal to the dipole, τ = [c_x p_y, −c_y p_x], with c calibrated linearly against a force sensor. The essential step is zeroing after the grasp. The authors’ code has no licence, so it was never read; everything comes from the equations in the paper. The sensor family is retrographic sensing (Johnson & Adelson 2009). Two things come from outside. Closed forms (Johnson, Contact Mechanics, CUP 1985): the pressure under a flat circular punch p = P/(2πa√(a²−r²)) (eq. 3.34) plus the antisymmetric term 3Mx/(2πa³√(a²−r²)) for a tilting moment (∫ x p dA = M, no lift-off for M ≤ Pa/3, derived), the Boussinesq half-space surface displacements of a normal point load (§3.2: ū_r = −(1−2ν)P/(4πGr), ū_z = (1−ν)P/(2πGr)), the surface displacements of a Hertz pressure (eqs. 3.41b, 3.42a), the elliptical Hertz pressure (eq. 4.24), no-slip torsion (Reissner–Sagoci) q_θ = 3M_z r/(4πa³√(a²−r²)) with β = 3M_z/(16Ga³), and the Cattaneo–Mindlin partial slip of the previous exhibit. Finite-element nodal displacements of a finite-thickness dome-shaped gel (shipped with the Robo-Touch/Taxim repository, MIT; used only when FULLSEYE_TAXIM_DATA is set, never copied into the repo). Three things are our own derivations, all turned into gates: Gauss’s law is exact in the half-space — since ∇·(r̂/r) = 2πδ² in two dimensions, ∇·ū = −(1−2ν)p/(2G), so the area-weighted dipole is proportional to the first moment of pressure (= the tilt moment M) regardless of the indenter’s shape (but the coefficient −(1−2ν)/(2G) vanishes as ν → 0.5: a real sensor’s signal comes from the bulging of the finite-thickness gel); the divergence of the Cerruti point-load field, −(1−ν)Qx/(2πGr³) — a pure shear produces a false tilt of (1−ν)/(1−2ν)·Q·R over the whole window (13·Q·R at ν 0.48), so the window is limited to the stick circle (the tilt charge concentrates at the edge singularity and wants a window ≥ a + 1.5 pitch; the two cannot both hold, so a small window gives a fixed fraction 0.47 that calibration absorbs); and the baseline form ( | u | as charge) is identically zero for a zeroed symmetric tilt because | u | is even in M. The subjects are existing ops: tacslip.marker_track (centroids and matching), the tacslip.cerruti_kernel convolution (an independent implementation of torsion), sceneflow.flow_divergence / flow_curl (second implementation on the grid), pivops.piv_vorticity (−curl under its (dy, dx) convention). New module tactorque, 17 ops. Dimensions: flat punch a = 3 mm, P = 2 N (lift-off at M = 2 N·mm), 16 mm field, 128 px gate grid (125 µm/px), 256 px image grid (62.5 µm/px), 0.5 mm markers. Figures: marker image + tracked vectors (×40) + dipole arrow (1:1 crop) with the divergence map of the tracked field, a GIF ramping M 0 → 1.5 N·mm (the dipole grows and the points land on the closed-form line), the decomposition maps (divergence → tilt, curl → torsion, mean → translation), FEM normal vs oblique (divergence scatter + radial profile), where it breaks (noise ∝ σ, a shear leak that moves six decades with the window radius, −57 % for a window 2 mm off-centre), and the coefficient for three shapes. 17 gates (the two FEM gates when the data is present; measured without the FEM data, gates alone 2.0 s, with figures 29 s): grid integrals of the punch pressure Σp h² = P to 0.57 % and Σx p h² = M to 0.86 % (the 1/√ edge) and the lift-off fail-closed, the Boussinesq kernel against 3.41b (0.03 %), 3.42a (0.01 %) and the punch u_z (0.35 %), the Gauss identity to 0.21 % (identical at ν 0.48 and 0.3, confirming the (1−2ν) scaling), zero dipole under pure normal load (8e-16, three forms), D ∝ M with R² = 1.000000, the coefficient within 0.035 % of the closed form, D_y/D_x 4e-4 and origin independence 1e-4 (net lattice charge −8e-4), the baseline form 3e-4, a coefficient spread over punch / sphere / ridge of 0.001 % dense and 0.25 % on the 0.5 mm lattice, torsion ∫r q dA = M_z 0.99, β −0.47 %, uniformity 0.14 %, rigid rotation −0.5 %, curl −0.5 %, piv_vorticity = −curl to 1e-16 (the flat punch’s q/(μp) peaks at 0.238, so no-slip is exact there), a Hertz contact’s partial-slip twist at 0.5 / 0.8 of full slip read by the default to ×0.999 / ×1.000 where the 0.4.0 no-slip relation over-reads ×1.330 / ×1.851 (fixed on 2026-10-06: the old gates synthesised and read with the same no-slip model, so they could not see it), the superposed decomposition (tilt 2.35 % with torsion→tilt cross-talk, 0.22 % alone; torsion 0.5 %; translation 0.038 px), the pure-shear leak ≤ 0.0012 N·mm inside the stick circle minus the fit radius vs 32.5 N·mm over the full window (derived 31.2), centroid noise 0.03 px → σ_M = 0.114 N·mm (window 1.5a, 252 markers), 0.72 over the full window and 0.038 at 0.01 px (∝ σ), 11/11 spelling-breaks plus nan-not-zero, scattered least squares = central differences to 1e-15 (5 points; the 9-point fit differs by 11 % at the edge), the image pipeline (M1 error 0.7 %, tracking RMS 0.0054 px, 961/961 matched), and the FEM: core divergence +0.019 with a ring of −0.0036 (bulging, opposite in sign to the half-space) and an oblique-load dipole ΔD_x = −6.4e-11 m³ for Δt_x = +19 µm (the shear-leak sign). Honestly: the half-space coefficient does not give the magnitude on a real (finite, nearly incompressible) gel — 1 N·mm is 0.06 px at ν 0.48, the same position as the paper’s calibration; three Johnson equation numbers (tilted punch, punch u_z, Reissner–Sagoci) were not checked against the text and are verified numerically by independent implementations; Lubkin’s 1951 closed form for partial-slip torsion is not implemented (partial slip is corrected with cuttouch’s numerical solution); the paper’s per-object calibration factors are a finite-thickness effect (the half-space is shape-free) and no real sensor was tested here; viscoelasticity, the time evolution of incipient slip and optical flow are out of scope. Traps: the 9-point (3×3) plane fit is a row-averaged difference and is 11 % off at the edge (5 points reproduce central differences), the mean curl/2 is 11 % low at the edge (hence the rigid-rotation least squares), torsion picks up the sub-pixel lattice phase and leaks 2 % into tilt, and the origin-independence gate breaks at the lattice’s net charge of 1e-3, so its threshold is 1e-3 rather than 1e-6. |
| *↑ Reading In-Grasp Tilt and Torsion Torque from a Tactile Sensor’s Marker Field with a ‘Tactile Dipole’ — Gauss’s Law Becoming an Identity in the Half-Space as the Gate ―― The third exhibit of the second physics-simulation × Fullseye round (the first read force from a normal indentation, the second shear and stick/slip from a marker array). The re-implemented method is Fuchioka & Hamaya, ICRA 2024 (arXiv 2404.15626): learning-free and optics-free, it treats the divergence ∇·v of the marker displacement field as a ‘charge’ (the normal-force distribution, by analogy with Gauss’s law), takes the dipole p = (1/N) Σ r_i (∇·v)_i about the midpoint of the positive and negative charge centroids, and reads the tilt torque orthogonal to the dipole, τ = [c_x p_y, −c_y p_x], with c calibrated linearly against a force sensor. The essential step is zeroing after the grasp. The authors’ code has no licence, so it was never read; everything comes from the equations in the paper. The sensor family is retrographic sensing (Johnson & Adelson 2009). Two things come from outside. Closed forms (Johnson, Contact Mechanics, CUP 1985): the pressure under a flat circular punch p = P/(2πa√(a²−r²)) (eq. 3.34) plus the antisymmetric term 3Mx/(2πa³√(a²−r²)) for a tilting moment (∫ x p dA = M, no lift-off for M ≤ Pa/3, derived), the Boussinesq half-space surface displacements of a normal point load (§3.2: ū_r = −(1−2ν)P/(4πGr), ū_z = (1−ν)P/(2πGr)), the surface displacements of a Hertz pressure (eqs. 3.41b, 3.42a), the elliptical Hertz pressure (eq. 4.24), no-slip torsion (Reissner–Sagoci) q_θ = 3M_z r/(4πa³√(a²−r²)) with β = 3M_z/(16Ga³), and the Cattaneo–Mindlin partial slip of the previous exhibit. Finite-element nodal displacements of a finite-thickness dome-shaped gel (shipped with the Robo-Touch/Taxim repository, MIT; used only when FULLSEYE_TAXIM_DATA is set, never copied into the repo). Three things are our own derivations, all turned into gates: Gauss’s law is exact in the half-space — since ∇·(r̂/r) = 2πδ² in two dimensions, ∇·ū = −(1−2ν)p/(2G), so the area-weighted dipole is proportional to the first moment of pressure (= the tilt moment M) regardless of the indenter’s shape (but the coefficient −(1−2ν)/(2G) vanishes as ν → 0.5: a real sensor’s signal comes from the bulging of the finite-thickness gel); the divergence of the Cerruti point-load field, −(1−ν)Qx/(2πGr³) — a pure shear produces a false tilt of (1−ν)/(1−2ν)·Q·R over the whole window (13·Q·R at ν 0.48), so the window is limited to the stick circle (the tilt charge concentrates at the edge singularity and wants a window ≥ a + 1.5 pitch; the two cannot both hold, so a small window gives a fixed fraction 0.47 that calibration absorbs); and the baseline form ( | u | as charge) is identically zero for a zeroed symmetric tilt because | u | is even in M. The subjects are existing ops: tacslip.marker_track (centroids and matching), the tacslip.cerruti_kernel convolution (an independent implementation of torsion), sceneflow.flow_divergence / flow_curl (second implementation on the grid), pivops.piv_vorticity (−curl under its (dy, dx) convention). New module tactorque, 17 ops. Dimensions: flat punch a = 3 mm, P = 2 N (lift-off at M = 2 N·mm), 16 mm field, 128 px gate grid (125 µm/px), 256 px image grid (62.5 µm/px), 0.5 mm markers. Figures: marker image + tracked vectors (×40) + dipole arrow (1:1 crop) with the divergence map of the tracked field, a GIF ramping M 0 → 1.5 N·mm (the dipole grows and the points land on the closed-form line), the decomposition maps (divergence → tilt, curl → torsion, mean → translation), FEM normal vs oblique (divergence scatter + radial profile), where it breaks (noise ∝ σ, a shear leak that moves six decades with the window radius, −57 % for a window 2 mm off-centre), and the coefficient for three shapes. 17 gates (the two FEM gates when the data is present; measured without the FEM data, gates alone 2.0 s, with figures 29 s): grid integrals of the punch pressure Σp h² = P to 0.57 % and Σx p h² = M to 0.86 % (the 1/√ edge) and the lift-off fail-closed, the Boussinesq kernel against 3.41b (0.03 %), 3.42a (0.01 %) and the punch u_z (0.35 %), the Gauss identity to 0.21 % (identical at ν 0.48 and 0.3, confirming the (1−2ν) scaling), zero dipole under pure normal load (8e-16, three forms), D ∝ M with R² = 1.000000, the coefficient within 0.035 % of the closed form, D_y/D_x 4e-4 and origin independence 1e-4 (net lattice charge −8e-4), the baseline form 3e-4, a coefficient spread over punch / sphere / ridge of 0.001 % dense and 0.25 % on the 0.5 mm lattice, torsion ∫r q dA = M_z 0.99, β −0.47 %, uniformity 0.14 %, rigid rotation −0.5 %, curl −0.5 %, piv_vorticity = −curl to 1e-16 (the flat punch’s q/(μp) peaks at 0.238, so no-slip is exact there), a Hertz contact’s partial-slip twist at 0.5 / 0.8 of full slip read by the default to ×0.999 / ×1.000 where the 0.4.0 no-slip relation over-reads ×1.330 / ×1.851 (fixed on 2026-10-06: the old gates synthesised and read with the same no-slip model, so they could not see it), the superposed decomposition (tilt 2.35 % with torsion→tilt cross-talk, 0.22 % alone; torsion 0.5 %; translation 0.038 px), the pure-shear leak ≤ 0.0012 N·mm inside the stick circle minus the fit radius vs 32.5 N·mm over the full window (derived 31.2), centroid noise 0.03 px → σ_M = 0.114 N·mm (window 1.5a, 252 markers), 0.72 over the full window and 0.038 at 0.01 px (∝ σ), 11/11 spelling-breaks plus nan-not-zero, scattered least squares = central differences to 1e-15 (5 points; the 9-point fit differs by 11 % at the edge), the image pipeline (M1 error 0.7 %, tracking RMS 0.0054 px, 961/961 matched), and the FEM: core divergence +0.019 with a ring of −0.0036 (bulging, opposite in sign to the half-space) and an oblique-load dipole ΔD_x = −6.4e-11 m³ for Δt_x = +19 µm (the shear-leak sign). Honestly: the half-space coefficient does not give the magnitude on a real (finite, nearly incompressible) gel — 1 N·mm is 0.06 px at ν 0.48, the same position as the paper’s calibration; three Johnson equation numbers (tilted punch, punch u_z, Reissner–Sagoci) were not checked against the text and are verified numerically by independent implementations; Lubkin’s 1951 closed form for partial-slip torsion is not implemented (partial slip is corrected with cuttouch’s numerical solution); the paper’s per-object calibration factors are a finite-thickness effect (the half-space is shape-free) and no real sensor was tested here; viscoelasticity, the time evolution of incipient slip and optical flow are out of scope. Traps: the 9-point (3×3) plane fit is a row-averaged difference and is 11 % off at the edge (5 points reproduce central differences), the mean curl/2 is 11 % low at the edge (hence the rigid-rotation least squares), torsion picks up the sub-pixel lattice phase and leaks 2 % into tilt, and the origin-independence gate breaks at the lattice’s net charge of 1e-3, so its threshold is 1e-3 rather than 1e-6.* |
↑ The measurement ―― 傾きモーメントを 0 → 1.5 N·mm に 7 段で上げる。左 = マーカー 1 個ごとの発散(赤 +・青 −、押し込み子の縁に正負の対が立つ)と変位(60 倍)、赤矢印 = 双極子(長さ ∝ M)。右 = 格子マーカーの双極子 D_x が閉形式の線 −(1−2ν)/2G·M1(破線)に乗る: R² = 1.000000、傾きの比 0.9997。 (figure labels are in Japanese; the numbers are the same)
↑ 重ね合わせた場(傾き 1 N·mm + ねじり 0.5 N·mm + 並進 1 px)を 3 つに分ける: 発散の双極子が傾き(M1 = 1.015 N·mm、真値 0.991、窓 a + 当てはめ半径)、剛体回転の最小二乗がねじり(M_z = 0.497 N·mm、真値 0.500、窓 0.9a)…
↑ 第 2 真値: 有限要素の節点変位(有限厚のドーム状ゲル、Robo-Touch/Taxim リポジトリ同梱、MIT、節点 15,230・間隔 0.155 mm)。
↑ 壊れる場所。
↑ 圧力の 1 次モーメントが同じ 1 N·mm の 3 つの押し込み子: 平頭にモーメント、Hertz 球を M/P だけずらす、細長い楕円(稜)をずらす。
py -3.11 examples/poc_tactile_dipole_torque.py
Source: examples/poc_tactile_dipole_torque.py
This run produced 6 figures in total - see them all
Ops used (notes): boussinesq_kernel · boussinesq_surface_displacement · cerruti_kernel · combined_modulus · dipole_to_torque_fit · dipole_torque_resolution · displace_markers · ellipse_pressure_shifted · fem_nodes_load · grasp_torque_frame · hertz_pressure · hertz_pressure_shifted · hertz_sphere · hertz_surface_ur · hertz_surface_uz · marker_divergence · marker_image · membrane_lights · membrane_markers · membrane_render_markers · membrane_render_rgb · membrane_shear_field · mindlin_partial_slip · pad_context … (+12)
The physics-simulation × Fullseye series continues (after pegsim, tacsim, tacslip, tactorque, puck and pegfail). The prior work on powder weighing (Kadokawa, Hamaya, Tanaka, IROS 2023, doi 10.1109/iros55552.2023.10342463) observes only the scale reading and has no vision. This exhibit fills in the other side — reading the angle of repose, volume, mass, flowability and discharge rate from the shape of the heap (a side image and a heightmap) — with the new module granular (23 ops plus 2 mujoco facades), learning-free. Ground truth comes from outside on four fronts. (1) Closed forms: the cone V = (π/3)R²H, H = R tan φ, m = ρ_b V; the Beverloo, Leniger and van de Velde (1961) discharge law W = C ρ_b √g (D₀ − k d)^{5/2} (C ≈ 0.58, k ≈ 1.4 as summarised by Nedderman 1992). (2) A published table: Table 1 of USP general chapter <1174> Powder Flow (angle of repose → flow property, after Carr 1965) — the table is in whole degrees, so the angle is rounded first (30.4 → excellent, 30.5 → good, below 25 is outside the table). (3) A published value: 25.2 ± 0.8 degrees for 1 mm glass beads (Sunday, Murdoch, Tardivel, Schwartz, Michel, MNRAS 2020, arXiv 2009.10448 §5.4); the way of measuring (separate lines on the left and right upper edges of a side image, tails and apex excluded, §5.3) is taken from the same paper. (4) A second implementation (–full): 1,200 rigid spheres in MuJoCo 3 (r 4 mm, sliding friction 0.16, rolling 0.09·R) flow out of a square orifice (48 mm) in a flat-bottomed hopper and build a heap. The side-image measurement reads the coverage of the edge pixel directly as a sub-pixel position (edge method): with uniform noise ±0.1 on the coverage it stays within 0.0064 degrees. ★The column-sum method (summed coverage = powder height) shrinks tan φ by the ratio 0.975 that clipping the noise to [0, 1] leaves in the powder pixels, and reads −0.63 degrees low — at first misread as a toe-rounding bias, until it survived with no rounding at all. The heightmap path takes the mode of the demops.dem_slope histogram (noise only ever raises the gradient: +0.12 degrees at σ 0.05 px, +0.37 at 0.1 px). ★A tilted datum β = 5 degrees adds to the angle of repose: the flanks read 33.62 / 26.10 degrees = the closed form atan(tan φ ± tan β), the 7.52-degree left-right difference raises the alarm (datum_tilt_check), and subtracting tangents brings back 30.0000. The heightmap mode reads 5.00 degrees — the ground itself, because the ground cells take over the histogram (29.9994 once the datum is passed). A sheared datum and a camera roll differ to first order on one flank (33.62 vs 35.00), so the two corrections must not be confused. Figures: the two lines fitted to a side image (1:1), the slope histogram, where resolution breaks it (heap widths 25–400 px), toe rounding, apex blunting, the tilted datum and a 50 px heap (×10, pixels visible), Beverloo’s W(D₀), the flowability bands with the published and MuJoCo values, the spoon tilt, and a GIF sweeping the datum tilt from −8 to +8 degrees; with –full: a GIF of the spheres flowing out of the orifice into a heap (bin walls and floor drawn), the lines fitted to the sphere heap’s side with the published 25.2-degree slope, the heightmap, the discharged mass against Beverloo’s slope, and a table of MuJoCo vs the outside references. 20 gates (numpy, 1.3 s) plus 5 with –full (MuJoCo, 34 s): cone round trips 1.8e-16, heightmap volume 3.5e-7, side-image round trip 0.0000 degrees, Beverloo exponent n = 2.5074 and C = 0.5894 from six synthetic discharge heightmap videos with σ 0.5 mm (noiseless n = 2.5000000), W(30 mm, 1 mm, 1500) = 0.3769 kg/s, toe rounding −0.248 → −0.001 degrees with 15 % of the toe excluded, apex blunting −0.554 → −0.004, resolution (noise ±0.1, 4 angles × 6 seeds: 0.197 degrees for a 50 px heap and 0.624 for 25 px from the side, 1.097 for 50 px from the heightmap), 16 flowability boundaries, ValueError for spelling breaks and for heaps that are absent or cut off, the spoon rule θ_c = φ − atan(2h₀/L) (our own derivation: 18.69 degrees at L 50 mm, h₀ 5 mm; φ as h₀ → 0. On 2026-10-05 the wedge slope was corrected from the small-angle tan φ − tan θ (20.67 degrees then) to the exact tan(φ − θ), and a container with no lip wall, which spills from θ = 0⁺, was added), container fill 0.6300 with a 3.000-degree surface, mass from video (the same cone on both sides, so it checks the plumbing only), the MJCF string and the entry points; –full: the heap formed (0 runaways, 114 left in the stagnant layer of the bin, 38.4 mm tall = 4.8 diameters), the mean of four azimuths (22.4 / 23.0 / 18.9 / 24.4) 22.17 degrees vs the published 25.2 ± 0.8 → −3.03 (gate −7 to +1), the median of the heightmap smoothed over two diameters 25.74 degrees (the unsmoothed mode 65.5 = sphere rims), discharge 0.906 kg/s vs Beverloo 0.911 kg/s (square orifice as an equivalent diameter of 54.2 mm, ρ_b 1312 kg/m³ measured in the bin). Honestly: the synthetic side image shares its edge model with the measurement, so the noiseless round trip checks the plumbing; the only independent subjects are the noise and MuJoCo’s spheres. Why the MuJoCo heap sits 3 degrees below the published value (rigid soft contact without cohesion, a rolling-friction model unlike DEM, a heap only five diameters tall, 18.9–24.4 degrees by azimuth from the square orifice’s flow) is not resolved. The 0.99 ratio to Beverloo agrees too well to be trusted and is kept as an order-of-magnitude gate (1.02 and 1.14 in earlier configurations; D₀/d = 6.8 is near the jamming limit; 14 frames in the band). The per-material table of Al-Hashemi & Al-Amoudi 2018 (CC BY 4.0) could not be retrieved and is not included; the empirical formula of Zhou et al. 2002 was not read. Milligram weighing is beyond the volume resolution of an image (it works for gram-scale gravel and beads; milligrams belong to the scale). Container fill assumes the image is the inner wall (wall detection is not implemented). Traps we hit: on a smooth floor (μ 0.16) the 1,200 spheres spread into a single layer 0.15 m in radius and no heap formed — a single-sphere test showed rolling resistance saturates at the sliding limit μ_s g (1.6 m/s² for rolling coefficients of both 0.01 and 0.1), so the floor was made rough with μ 1.0 (as experiments glue sandpaper to the base); a soft-contact time constant below 2·timestep diverges (the op refuses it); the Newton solver stalls on a dense heap beyond 280 s → CG with an elliptic cone.
↑ Measuring a Powder Heap from Images — Angle of Repose, Volume, Mass, Flowability and Discharge by Rules, with Closed Forms, Published Values and MuJoCo as the Truth ―― The physics-simulation × Fullseye series continues (after pegsim, tacsim, tacslip, tactorque, puck and pegfail). The prior work on powder weighing (Kadokawa, Hamaya, Tanaka, IROS 2023, doi 10.1109/iros55552.2023.10342463) observes only the scale reading and has no vision. This exhibit fills in the other side — reading the angle of repose, volume, mass, flowability and discharge rate from the shape of the heap (a side image and a heightmap) — with the new module granular (23 ops plus 2 mujoco facades), learning-free. Ground truth comes from outside on four fronts. (1) Closed forms: the cone V = (π/3)R²H, H = R tan φ, m = ρ_b V; the Beverloo, Leniger and van de Velde (1961) discharge law W = C ρ_b √g (D₀ − k d)^{5/2} (C ≈ 0.58, k ≈ 1.4 as summarised by Nedderman 1992). (2) A published table: Table 1 of USP general chapter <1174> Powder Flow (angle of repose → flow property, after Carr 1965) — the table is in whole degrees, so the angle is rounded first (30.4 → excellent, 30.5 → good, below 25 is outside the table). (3) A published value: 25.2 ± 0.8 degrees for 1 mm glass beads (Sunday, Murdoch, Tardivel, Schwartz, Michel, MNRAS 2020, arXiv 2009.10448 §5.4); the way of measuring (separate lines on the left and right upper edges of a side image, tails and apex excluded, §5.3) is taken from the same paper. (4) A second implementation (–full): 1,200 rigid spheres in MuJoCo 3 (r 4 mm, sliding friction 0.16, rolling 0.09·R) flow out of a square orifice (48 mm) in a flat-bottomed hopper and build a heap. The side-image measurement reads the coverage of the edge pixel directly as a sub-pixel position (edge method): with uniform noise ±0.1 on the coverage it stays within 0.0064 degrees. ★The column-sum method (summed coverage = powder height) shrinks tan φ by the ratio 0.975 that clipping the noise to [0, 1] leaves in the powder pixels, and reads −0.63 degrees low — at first misread as a toe-rounding bias, until it survived with no rounding at all. The heightmap path takes the mode of the demops.dem_slope histogram (noise only ever raises the gradient: +0.12 degrees at σ 0.05 px, +0.37 at 0.1 px). ★A tilted datum β = 5 degrees adds to the angle of repose: the flanks read 33.62 / 26.10 degrees = the closed form atan(tan φ ± tan β), the 7.52-degree left-right difference raises the alarm (datum_tilt_check), and subtracting tangents brings back 30.0000. The heightmap mode reads 5.00 degrees — the ground itself, because the ground cells take over the histogram (29.9994 once the datum is passed). A sheared datum and a camera roll differ to first order on one flank (33.62 vs 35.00), so the two corrections must not be confused. Figures: the two lines fitted to a side image (1:1), the slope histogram, where resolution breaks it (heap widths 25–400 px), toe rounding, apex blunting, the tilted datum and a 50 px heap (×10, pixels visible), Beverloo’s W(D₀), the flowability bands with the published and MuJoCo values, the spoon tilt, and a GIF sweeping the datum tilt from −8 to +8 degrees; with –full: a GIF of the spheres flowing out of the orifice into a heap (bin walls and floor drawn), the lines fitted to the sphere heap’s side with the published 25.2-degree slope, the heightmap, the discharged mass against Beverloo’s slope, and a table of MuJoCo vs the outside references. 20 gates (numpy, 1.3 s) plus 5 with –full (MuJoCo, 34 s): cone round trips 1.8e-16, heightmap volume 3.5e-7, side-image round trip 0.0000 degrees, Beverloo exponent n = 2.5074 and C = 0.5894 from six synthetic discharge heightmap videos with σ 0.5 mm (noiseless n = 2.5000000), W(30 mm, 1 mm, 1500) = 0.3769 kg/s, toe rounding −0.248 → −0.001 degrees with 15 % of the toe excluded, apex blunting −0.554 → −0.004, resolution (noise ±0.1, 4 angles × 6 seeds: 0.197 degrees for a 50 px heap and 0.624 for 25 px from the side, 1.097 for 50 px from the heightmap), 16 flowability boundaries, ValueError for spelling breaks and for heaps that are absent or cut off, the spoon rule θ_c = φ − atan(2h₀/L) (our own derivation: 18.69 degrees at L 50 mm, h₀ 5 mm; φ as h₀ → 0. On 2026-10-05 the wedge slope was corrected from the small-angle tan φ − tan θ (20.67 degrees then) to the exact tan(φ − θ), and a container with no lip wall, which spills from θ = 0⁺, was added), container fill 0.6300 with a 3.000-degree surface, mass from video (the same cone on both sides, so it checks the plumbing only), the MJCF string and the entry points; –full: the heap formed (0 runaways, 114 left in the stagnant layer of the bin, 38.4 mm tall = 4.8 diameters), the mean of four azimuths (22.4 / 23.0 / 18.9 / 24.4) 22.17 degrees vs the published 25.2 ± 0.8 → −3.03 (gate −7 to +1), the median of the heightmap smoothed over two diameters 25.74 degrees (the unsmoothed mode 65.5 = sphere rims), discharge 0.906 kg/s vs Beverloo 0.911 kg/s (square orifice as an equivalent diameter of 54.2 mm, ρ_b 1312 kg/m³ measured in the bin). Honestly: the synthetic side image shares its edge model with the measurement, so the noiseless round trip checks the plumbing; the only independent subjects are the noise and MuJoCo’s spheres. Why the MuJoCo heap sits 3 degrees below the published value (rigid soft contact without cohesion, a rolling-friction model unlike DEM, a heap only five diameters tall, 18.9–24.4 degrees by azimuth from the square orifice’s flow) is not resolved. The 0.99 ratio to Beverloo agrees too well to be trusted and is kept as an order-of-magnitude gate (1.02 and 1.14 in earlier configurations; D₀/d = 6.8 is near the jamming limit; 14 frames in the band). The per-material table of Al-Hashemi & Al-Amoudi 2018 (CC BY 4.0) could not be retrieved and is not included; the empirical formula of Zhou et al. 2002 was not read. Milligram weighing is beyond the volume resolution of an image (it works for gram-scale gravel and beads; milligrams belong to the scale). Container fill assumes the image is the inner wall (wall detection is not implemented). Traps we hit: on a smooth floor (μ 0.16) the 1,200 spheres spread into a single layer 0.15 m in radius and no heap formed — a single-sphere test showed rolling resistance saturates at the sliding limit μ_s g (1.6 m/s² for rolling coefficients of both 0.01 and 0.1), so the floor was made rough with μ 1.0 (as experiments glue sandpaper to the base); a soft-contact time constant below 2·timestep diverges (the op refuses it); the Newton solver stalls on a dense heap beyond 280 s → CG with an elliptic cone.
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_granular_heap_repose/02_granular_slope_histogram.png) |
| *↑ The measurement ―― 高さ図の勾配ヒストグラム(demops.dem_slope)。雑音なしは 1 ビンに立つ。σ = 0.1 px の雑音で最頻が +0.12 度ずれる( | ∇ | は雑音で増えるだけ)—— 同じ大きさの雑音で側面像は 0.01 度も動かない。 (figure labels are in Japanese; the numbers are the same)* |
↑ 山の幅(画素)と φ の最大誤差(2 角度 × 8 seed)。
↑ 基準面が 5 度傾くと左右が 33.62 / 26.10 度に割れる(atan(tan φ ± tan β))。
↑ 安息角 → 流動性区分(USP <1174> 表 1、原典 Carr 1965)。
↑ 同じ山の高さ図(単位 mm、球面の最大高さ、1.5 mm/セルを 3 倍の最近傍で表示)。
↑ The animation ―― 基準面の傾き β を −8〜+8 度で振る(32 コマ)。左右の斜面が atan(tan φ ± tan β) で割れ、左右差 ≈ 2β が 1 度を超えると警報。tan の引き算で 30 度に戻る。
↑ The animation ―― 剛体球 1200 個(r 4 mm、滑り摩擦 0.16、転がり 0.09·R、粗い台)が平底ホッパ(青灰の線 = 壁と底板)の正方孔(48 mm)から流れて山になる(0.5 mm/px、67 コマ)。φ = 22.17 度(4 方位の平均)。
py -3.11 examples/poc_granular_heap_repose.py
Source: examples/poc_granular_heap_repose.py
This run produced 16 figures in total - see them all
Ops used (notes): beverloo_fit · beverloo_rate · container_fill_level · container_synth · datum_tilt_check · difference · discharge_synth · dispense_mass_from_video · heap_mass · heap_scene_mjcf · heap_spheres_select · heap_synth_cone · heap_volume_cone · heap_volume_heightmap · hopper_discharge_rate · powder_flowability_class · repose_angle_heightmap · repose_angle_silhouette · spheres_render_shaded · spheres_to_heightmap · spheres_to_silhouette · spoon_tilt_critical · spoon_tilt_dispense · text_box
The physics-simulation × Fullseye series. The subject is robotic food slicing (arXiv:2404.02569, ICRA 2024). Without learning, rules alone close the chain image → knife height → soft-wrist deflection → force → toughness R. New module cutting, 16 ops plus 1 mujoco facade. Ground truth comes from outside on three fronts. (1) Closed-form cutting mechanics: equations 1.1–1.4 of Atkins, Interface Focus 6:20160019 (2016) (frictionless push + slice, V/(Rw) = 1/(1+ξ²), H = ξV, F_res/(Rw) = 1/√(1+ξ²)) with the numbers in the text (H/Rw peaks at 0.5 at ξ = 1; a tilted blade moved vertically gives ξ = tan i), and equation 2.6 of Williams & Patel, Interface Focus 6:20150108 (2016) (wedge + Coulomb friction, minimum 1/(1 − sin β)) with the worked example (μ = 0.2 gives θo = 79° and a minimum of 1.24). The slice/push equations with friction and blade angle were not read, so they are not implemented and the combination raises ValueError. (2) The physics engine MuJoCo (–full): the blade rendered by an orthographic camera is followed by the same tracker and compared with the wrist constraint force, where the food resistance is replaced each step by a joint friction loss R·w_eff·g(ξ). A soft body matches the stiffness order of magnitude but does not fracture (the blade sinks in), and a gate pins that. (3) Finite-element blade forces (–full, optional, non-commercial): the CSV published with arXiv:2105.12244 (CC BY-NC 4.0) is read from an environment variable and compared by shape only (the data is not in the repo and not in the figures). ★Slice-thickness edges are read by unmixing each row into three colours (background, food, blade): the background fraction steps 1 → 0 at the end face and the blade fraction 0 → 1 at the blade face, so the sum inside a window is the edge position. Blur does not change the sum, so widening the window from the step’s second moment removes the bias — the thickness bias at 4 px blur goes from −18 µm to +11 µm, and 8 px is refused because the blade band is too thin for the blur (the prototype passed silently at −37 µm). ★For a thin slice the reference food colour comes from the body beyond the blade, so a left-right illumination gradient shifts it by 13 %: estimating the illumination from the blade band’s brightness and correcting brings 66 µm down to 5 µm. Figures: a GIF of a synthetic cut (estimated edge line, dashed where hidden, truth, tip and the force curve), the edge-on image with both edges, the thickness distribution and scatter over 40 images, the cut-surface profile, where it breaks, the slice/push closed form; with –full: a MuJoCo GIF and the force of a blade pressed into a soft body. 14 gates (numpy, CPU 0.95 s) plus 7 with –full: closed forms 1e-16, H/Rw maximum 0.5000 at ξ 1.000, θo 78.69° and minimum 1.2440, angle 0.017°, hidden mid-span edge 8.2 µm, tip 0.24 mm, depth 29 µm (0.12 px), thickness 0.1–5 mm with bias 6.9 µm and per-row RMS 18.5 µm, tilt 0.021°, roughness floor Rq 11 µm, R 393.6 (truth 400, −1.6 %) and ξ 0.561 (truth 0.570), R 500.0 in a world with 1.25× force (self-reported); –full (8 px/mm and 20 px/mm): angle 0.0014°, edge 1.1 µm, depth 2.2 µm, thickness bias 2.8 µm and RMS 10.4 µm, ≤ 5 µm up to noise 0.05 with 0.1 refused, MuJoCo soft body F/(E·A·δ/H) 1.106 / 1.074 / 1.059, the blade saturating at 0.76 N, render → track 0.9 µm and 0.0007°, wrist force 0.083 N ≤ c·v 0.150 N, FEM residual / steady value ≤ 0.068 over 6 curves. Honestly: no force truth comes from outside — the synthetic forces and the fit use the same Atkins model, so the image → R chain checks the plumbing (in a world with 1.25× force, R simply comes out 1.25× and toughness cannot be separated from friction). The food is assumed not to deform, the blade edge straight and single-bevelled. No measured toughness values are quoted. No public cutting dataset with images was found. The MuJoCo soft body keeps oscillating by ±5–11 % while held. Traps we hit: the image centre is ((H−1)/2, (W−1)/2) (H/2 shifts it by 62.5 µm), MuJoCo’s friction loss is viscous-soft by default, and estimating ξ from the entry part of the trajectory gives 0.63 (truth 0.57).
↑ Measuring Food Cutting from Images — Knife Tracking, Slice Thickness, Cut-Surface Roughness and Cutting Force from Wrist Deflection, with Closed Forms and MuJoCo as the Truth ―― The physics-simulation × Fullseye series. The subject is robotic food slicing (arXiv:2404.02569, ICRA 2024). Without learning, rules alone close the chain image → knife height → soft-wrist deflection → force → toughness R. New module cutting, 16 ops plus 1 mujoco facade. Ground truth comes from outside on three fronts. (1) Closed-form cutting mechanics: equations 1.1–1.4 of Atkins, Interface Focus 6:20160019 (2016) (frictionless push + slice, V/(Rw) = 1/(1+ξ²), H = ξV, F_res/(Rw) = 1/√(1+ξ²)) with the numbers in the text (H/Rw peaks at 0.5 at ξ = 1; a tilted blade moved vertically gives ξ = tan i), and equation 2.6 of Williams & Patel, Interface Focus 6:20150108 (2016) (wedge + Coulomb friction, minimum 1/(1 − sin β)) with the worked example (μ = 0.2 gives θo = 79° and a minimum of 1.24). The slice/push equations with friction and blade angle were not read, so they are not implemented and the combination raises ValueError. (2) The physics engine MuJoCo (–full): the blade rendered by an orthographic camera is followed by the same tracker and compared with the wrist constraint force, where the food resistance is replaced each step by a joint friction loss R·w_eff·g(ξ). A soft body matches the stiffness order of magnitude but does not fracture (the blade sinks in), and a gate pins that. (3) Finite-element blade forces (–full, optional, non-commercial): the CSV published with arXiv:2105.12244 (CC BY-NC 4.0) is read from an environment variable and compared by shape only (the data is not in the repo and not in the figures). ★Slice-thickness edges are read by unmixing each row into three colours (background, food, blade): the background fraction steps 1 → 0 at the end face and the blade fraction 0 → 1 at the blade face, so the sum inside a window is the edge position. Blur does not change the sum, so widening the window from the step’s second moment removes the bias — the thickness bias at 4 px blur goes from −18 µm to +11 µm, and 8 px is refused because the blade band is too thin for the blur (the prototype passed silently at −37 µm). ★For a thin slice the reference food colour comes from the body beyond the blade, so a left-right illumination gradient shifts it by 13 %: estimating the illumination from the blade band’s brightness and correcting brings 66 µm down to 5 µm. Figures: a GIF of a synthetic cut (estimated edge line, dashed where hidden, truth, tip and the force curve), the edge-on image with both edges, the thickness distribution and scatter over 40 images, the cut-surface profile, where it breaks, the slice/push closed form; with –full: a MuJoCo GIF and the force of a blade pressed into a soft body. 14 gates (numpy, CPU 0.95 s) plus 7 with –full: closed forms 1e-16, H/Rw maximum 0.5000 at ξ 1.000, θo 78.69° and minimum 1.2440, angle 0.017°, hidden mid-span edge 8.2 µm, tip 0.24 mm, depth 29 µm (0.12 px), thickness 0.1–5 mm with bias 6.9 µm and per-row RMS 18.5 µm, tilt 0.021°, roughness floor Rq 11 µm, R 393.6 (truth 400, −1.6 %) and ξ 0.561 (truth 0.570), R 500.0 in a world with 1.25× force (self-reported); –full (8 px/mm and 20 px/mm): angle 0.0014°, edge 1.1 µm, depth 2.2 µm, thickness bias 2.8 µm and RMS 10.4 µm, ≤ 5 µm up to noise 0.05 with 0.1 refused, MuJoCo soft body F/(E·A·δ/H) 1.106 / 1.074 / 1.059, the blade saturating at 0.76 N, render → track 0.9 µm and 0.0007°, wrist force 0.083 N ≤ c·v 0.150 N, FEM residual / steady value ≤ 0.068 over 6 curves. Honestly: no force truth comes from outside — the synthetic forces and the fit use the same Atkins model, so the image → R chain checks the plumbing (in a world with 1.25× force, R simply comes out 1.25× and toughness cannot be separated from friction). The food is assumed not to deform, the blade edge straight and single-bevelled. No measured toughness values are quoted. No public cutting dataset with images was found. The MuJoCo soft body keeps oscillating by ±5–11 % while held. Traps we hit: the image centre is ((H−1)/2, (W−1)/2) (H/2 shifts it by 62.5 µm), MuJoCo’s friction loss is viscous-soft by default, and estimating ξ from the entry part of the trajectory gives 0.63 (truth 0.57).
↑ The measurement ―― 刃先方向の像(20 px/mm、等倍): 片刃の平らな面と食材の端面の距離 = 切片の厚み。破線 = 推定の両縁。 (figure labels are in Japanese; the numbers are the same)
↑ 狙い 1.5 mm・ばらつき σ 0.08 mm・傾き σ 0.5° の 40 枚: 真値(破線)と画像(実線)の分布。
↑ 40 枚の測った厚み vs 真値(破線 = y = x)。
↑ 雑音とぼけを強めたときの深さ・厚みの誤差(元の解像度)。
↑ 摩擦なしの slice/push の閉形式。
↑ The animation ―― MuJoCo の正射影カメラで描いた刃(柔らかい手首 4 N/mm、抵抗 = 摩擦損失 = Atkins の切断力)を同じ追跡器で追う。破線 = 推定の刃先線。
py -3.11 examples/poc_food_cutting_measure.py
Source: examples/poc_food_cutting_measure.py
This run produced 9 figures in total - see them all
Ops used (notes): cut_depth_from_side · cut_force_atkins · cut_force_csv_load · cut_force_fit · cut_surface_roughness · cutting_edge_render · cutting_episode_synth · cutting_face_render · cutting_scene · cutting_wrist_mjcf · food_cut_width · force_from_wrist_displacement · identity · knife_edge_track · profile_params · slice_push_from_track · slice_push_ratio · slice_thickness_profile · text_box · zoom_inset
The physics-simulation × Fullseye series (vision-based touch), the next step beyond tacsim (a Hertz membrane, small strain). The primary source is the PDF of T. Mu et al., “A scaling law for large-deformation contact in soft materials” (arXiv:2509.18581, 2025). It is set up as a soft dome fingertip sensor (a hemisphere, height L = radius R = 8 mm, μ = 0.1 MPa as design assumptions) pressed onto a flat object. The framework: the linear solution F_L = C δⁿ for a profile f = c rᵖ (n = 1 + 1/p), large deformation F = κₙ(δ/L) F_L, the contact radius of eq. (4) (incomplete beta) and the universal form κ = (1 − 10/9·δ/L)⁻¹. New module tacdome, 12 ops, all numpy. ★The linear coefficient of eq. (3): the print, read from a 400 dpi image of the page, says (4+2n)/(1+n), which gives κ₁(0.5) = −1.667 for a cylinder (a negative force). Integrating the paper’s own model (a neo-Hookean cylindrical spring of length L − g at every point of the 1-D profile) gives c₁ = 2n/(1+n) and c₂ = n/(2+n), and a contact radius identical to eq. (4) — the quadratic term and eq. (4) agree with the print; only the linear term differs. A variant with all springs of length L gives (1.008, 0.258), matching neither reading. The appendix is not on arXiv and was not read. Ground truth from outside on three fronts: the exact uniaxial compression of an incompressible neo-Hookean cylinder (1.9e-15 from the derived κ₁), the small-strain Hertz limit (1e-12 from tacsim, the subject), and the paper’s universal k = 10/9 (least squares of the derived κ₁⁻¹ gives 1.1060, 0.5 % off). Second implementation = midpoint rule over the spring bed (8.5e-9 from the closed form); three routes to eq. (4) agree to 1e-15. The derived κ⁻¹(0.5) = 0.429 / 0.493 / 0.545 (n = 1 / 1.5 / 2) has the same order as the inset of the paper’s Fig. 4B. The subject’s result — how wrong it is depends on what you observe: for a hemispherical fingertip, Hertz force read from indentation is −4.10 / −27.79 / −50.70 % at δ/L = 0.05 / 0.3 / 0.5 and the radius −1.71 / −11.70 / −22.20 %. But reading force from the contact radius with Hertz (how a vision-based tactile sensor reads it), κ and the radius growth nearly cancel: max +5.39 % (d = 0.4). A cone does not cancel (−18.95 % at d = 0.5); a flat punch cannot be read, since its radius does not fix δ. From the internal camera image (192 px, 0.117 mm/px) the area method (a linear sum of unclipped coverage) gives the radius within 0.004 %, the inverted force within 0.013 % and 0.411 % with noise σ = 0.03; fitting measure.fit_circle to edge pixels gives −0.61 to −2.51 %. With –full: 0.0049 % radius and 0.0146 % force over 20 points at 512 px; a MuJoCo soft body (a linear-elastic cube compressed without friction) matches only the linear limit (stiffness ratio 0.98–0.99 at small strain) and collapses to 0.18 at ε = 0.31 — useless as a large-deformation truth (gated at small strain only). Figures: a GIF of the press (internal camera image, spring bed, force curve), force for three shapes, the κ⁻¹ band and three readings, a/a_L with the exact cylinder, the Hertz error, a 24× crop of the contact edge; with –full: the soft-body stiffness ratio. 13 gates (default, 0.2 s) plus 2 with –full (41 s with figures). Honestly: Fig. 5C (radius vs force) is not reproduced (the derived model differs by 0–1.7 % in radius at equal force; the sensor dimensions in the appendix were not read). Nor is “all shapes fall in a narrow band described by the universal form” (the universal form is 10.9 % above the sphere and 22.7 % above the cone). The cylinder experiments include friction and are not gated. The hemisphere is approximated by a paraboloid; frictionless, quasi-static, incompressible; idealised lighting. Trap we hit: the figure captions printed the pixel pitch in metres labelled as mm (0.000 mm/px), fixed on inspection during integration.
↑ Gating Large-Deformation Contact of a Soft Dome Fingertip Pressed on a Flat Object with a Scaling Law and an Exact Continuum Solution — As Printed, the Linear Coefficient of Eq. (3) Makes the Force of a Cylinder Negative; 2n/(1+n) Is Derived from the Paper’s Own Model ―― The physics-simulation × Fullseye series (vision-based touch), the next step beyond tacsim (a Hertz membrane, small strain). The primary source is the PDF of T. Mu et al., “A scaling law for large-deformation contact in soft materials” (arXiv:2509.18581, 2025). It is set up as a soft dome fingertip sensor (a hemisphere, height L = radius R = 8 mm, μ = 0.1 MPa as design assumptions) pressed onto a flat object. The framework: the linear solution F_L = C δⁿ for a profile f = c rᵖ (n = 1 + 1/p), large deformation F = κₙ(δ/L) F_L, the contact radius of eq. (4) (incomplete beta) and the universal form κ = (1 − 10/9·δ/L)⁻¹. New module tacdome, 12 ops, all numpy. ★The linear coefficient of eq. (3): the print, read from a 400 dpi image of the page, says (4+2n)/(1+n), which gives κ₁(0.5) = −1.667 for a cylinder (a negative force). Integrating the paper’s own model (a neo-Hookean cylindrical spring of length L − g at every point of the 1-D profile) gives c₁ = 2n/(1+n) and c₂ = n/(2+n), and a contact radius identical to eq. (4) — the quadratic term and eq. (4) agree with the print; only the linear term differs. A variant with all springs of length L gives (1.008, 0.258), matching neither reading. The appendix is not on arXiv and was not read. Ground truth from outside on three fronts: the exact uniaxial compression of an incompressible neo-Hookean cylinder (1.9e-15 from the derived κ₁), the small-strain Hertz limit (1e-12 from tacsim, the subject), and the paper’s universal k = 10/9 (least squares of the derived κ₁⁻¹ gives 1.1060, 0.5 % off). Second implementation = midpoint rule over the spring bed (8.5e-9 from the closed form); three routes to eq. (4) agree to 1e-15. The derived κ⁻¹(0.5) = 0.429 / 0.493 / 0.545 (n = 1 / 1.5 / 2) has the same order as the inset of the paper’s Fig. 4B. The subject’s result — how wrong it is depends on what you observe: for a hemispherical fingertip, Hertz force read from indentation is −4.10 / −27.79 / −50.70 % at δ/L = 0.05 / 0.3 / 0.5 and the radius −1.71 / −11.70 / −22.20 %. But reading force from the contact radius with Hertz (how a vision-based tactile sensor reads it), κ and the radius growth nearly cancel: max +5.39 % (d = 0.4). A cone does not cancel (−18.95 % at d = 0.5); a flat punch cannot be read, since its radius does not fix δ. From the internal camera image (192 px, 0.117 mm/px) the area method (a linear sum of unclipped coverage) gives the radius within 0.004 %, the inverted force within 0.013 % and 0.411 % with noise σ = 0.03; fitting measure.fit_circle to edge pixels gives −0.61 to −2.51 %. With –full: 0.0049 % radius and 0.0146 % force over 20 points at 512 px; a MuJoCo soft body (a linear-elastic cube compressed without friction) matches only the linear limit (stiffness ratio 0.98–0.99 at small strain) and collapses to 0.18 at ε = 0.31 — useless as a large-deformation truth (gated at small strain only). Figures: a GIF of the press (internal camera image, spring bed, force curve), force for three shapes, the κ⁻¹ band and three readings, a/a_L with the exact cylinder, the Hertz error, a 24× crop of the contact edge; with –full: the soft-body stiffness ratio. 13 gates (default, 0.2 s) plus 2 with –full (41 s with figures). Honestly: Fig. 5C (radius vs force) is not reproduced (the derived model differs by 0–1.7 % in radius at equal force; the sensor dimensions in the appendix were not read). Nor is “all shapes fall in a narrow band described by the universal form” (the universal form is 10.9 % above the sphere and 22.7 % above the cone). The cylinder experiments include friction and are not gated. The hemisphere is approximated by a paraboloid; frictionless, quasi-static, incompressible; idealised lighting. Trap we hit: the figure captions printed the pixel pitch in metres labelled as mm (0.000 mm/px), fixed on inspection during integration.
↑ The measurement ―― 球(p = 2、n = 1.5)・円錐(p = 1、n = 2、傾き 1)・平頭の円柱(p = ∞、n = 1、半径 = L)の力(無次元、縦軸は対数)。破線 = 線形解(球は Hertz)、実線 = 大変形 F = κₙ F_L。δ/L = 0.3 を超えると開き、平頭が最も大きく外れる(全ばねが最大ひずみ)。 (figure labels are in Japanese; the numbers are the same)
↑ 導出した κₙ⁻¹(実線、n = 1 / 1.5 / 2)は n = 2 が上 = 論文の図 4B の挿入図と同じ並び。
↑ 式 (4) の a/a_L(積分の形で計算、不完全ベータの形と 1e-12 で一致)。
↑ 小変形の Hertz を大変形に当てた誤差(寸法・弾性率に依らない)。
↑ 左 = d = 0.3 の内側カメラの接触像(192 px、0.117 mm/px、画素を 2 倍、赤 = 面積法で読んだ円、黄 = 右の範囲)。
py -3.11 examples/poc_tacdome_large_deformation.py
Source: examples/poc_tacdome_large_deformation.py
This run produced 7 figures in total - see them all
Ops used (notes): contact_patch_radius · dome_contact_image · hertz_force · hertz_small_strain_error · hertz_sphere · large_deformation_contact · large_deformation_inverse · largedef_correction · largedef_radius_ratio · largedef_universal_correction · mdr_spring_bed · neohookean_cylinder_exact · powerlaw_linear_contact
The physics-simulation × Fullseye series continues. Imitation learning for grinding (DIPCOM, arXiv:2410.19235) and variable-compliance wiping (Comp-ACT, arXiv:2406.14990) learn the stiffness that keeps contact. The ledger had parked them as “thin on vision”, but the result of the work (how much was removed, what area was wiped) can be measured from images, so they are built here as parts that remain without learning. New module polish, 13 ops plus 3 mujoco facades. From outside: Preston’s law dh/dt = k_p p v (the 1927 original was not read; the form and the order of k_p, 2e-13 to 2e-12 m²/N for ceria on glass, come from eq. (1) and the text of Shen et al. 2018, J. Am. Ceram. Soc.), contact pressure (uniform for a flat pad, Hertz for a sphere via tacsim’s hertz_sphere), derived closed forms (the profile of a straight stroke, the wiped band width and the minimum force that wipes, the area of parallel strokes, and roughness decaying as exp(−k_p k_w v t) on an elastic-bed pad), and MuJoCo. (1) Two implementations of the removal map (direct integration per segment / FFT convolution of the path’s line density with the pressure window) differ by 0.37 % of peak rms, volume ratio 3.6e-4. (2) The profile of one stroke matches the closed forms (flat 2k_p p √(a² − y²); Hertz k_p (E*/R)(a² − y²), whose curvature does not depend on force; an asinh form for a spinning flat pad) within 0.26 %, volume per length = k_p F (0.99964 / 1.00002). (3) The band width read with Otsu from a film image (Beer–Lambert, 1 % noise) matches the closed form within 0.28 % for flat and Hertz tools at four forces each. Trap: Otsu’s threshold means a residual film of about 1/3 of h0; forgetting that is up to 36 % off. Below the minimum wiping force of 1.90 N no pixel is clean; the area of four parallel strokes is within 0.30 % at four pitches (placing the pattern on pixel centres drops a whole column at the edge, −1.7 %; it is offset by 0.3 px). (4) From before/after height maps (20 nm roughness, 1 nm noise, re-mounting) the removed depth of 240 nm is read with 1.41 nm rms and Preston’s coefficient within 0.04 % (using the outer frame as the reference is 42 % off). (5) Elastic bed: while the whole surface touches, the rms stays within 0.20 % of the exponential, Sq ratio 0.1351 vs 0.1353 from roughness. When only the peaks touch it is 19 % off. (6) MuJoCo (–full): a cylindrical tool is pressed on a plate through a wrist spring (300 N/m and 10 kN/m) and moved. The compliant wrist’s force follows the spring’s closed form (99th percentile 0.05 %), friction = μ; the stiff wrist lifts off at 19.2 mm against 19.8 mm in closed form. The band width wiped with MuJoCo’s forces matches the closed form of a stroke with varying force (median 0.55 % / 0.17 %). Band-width variation: 0.02 % compliant, 13 % stiff, 83 / 309 columns left unwiped: why one wipes with variable compliance, shown from band-width images without learning. Figures: a moving picture of parallel wiping, stroke profiles vs closed form, band width vs force, height maps and the depth reading, roughness decay on the elastic bed, area vs pitch; with –full: the force along a stroke and the bands wiped with MuJoCo’s forces. 10 gates (default, 1.9 s) plus 5 with –full (15 s with figures). Honestly: the film-removal coefficient for wiping is a placeholder (the band gate checks that the closed form and the image agree for the same coefficient). The flat pad’s uniform pressure assumes a rigid flat (the Boussinesq edge pressure is not included). The elastic bed is an assumption, and the height maps assume no lateral shift. MuJoCo’s default contact was too soft and gave forces 6–17 % low, so the contact was stiffened and an elliptic cone used. Chip formation in grinding is not modelled.
↑ Measuring Grinding, Polishing and Wiping from Images — Removed Depth, Wiped Band Width and Area Checked by Preston’s Law, Closed-Form Contact Pressure and MuJoCo; a Compliant Wrist Keeps the Band Even over a 2 mm Height Error while a Stiff Wrist Lifts Off and Leaves Gaps ―― The physics-simulation × Fullseye series continues. Imitation learning for grinding (DIPCOM, arXiv:2410.19235) and variable-compliance wiping (Comp-ACT, arXiv:2406.14990) learn the stiffness that keeps contact. The ledger had parked them as “thin on vision”, but the result of the work (how much was removed, what area was wiped) can be measured from images, so they are built here as parts that remain without learning. New module polish, 13 ops plus 3 mujoco facades. From outside: Preston’s law dh/dt = k_p p v (the 1927 original was not read; the form and the order of k_p, 2e-13 to 2e-12 m²/N for ceria on glass, come from eq. (1) and the text of Shen et al. 2018, J. Am. Ceram. Soc.), contact pressure (uniform for a flat pad, Hertz for a sphere via tacsim’s hertz_sphere), derived closed forms (the profile of a straight stroke, the wiped band width and the minimum force that wipes, the area of parallel strokes, and roughness decaying as exp(−k_p k_w v t) on an elastic-bed pad), and MuJoCo. (1) Two implementations of the removal map (direct integration per segment / FFT convolution of the path’s line density with the pressure window) differ by 0.37 % of peak rms, volume ratio 3.6e-4. (2) The profile of one stroke matches the closed forms (flat 2k_p p √(a² − y²); Hertz k_p (E/R)(a² − y²), whose curvature does not depend on force; an asinh form for a spinning flat pad) within 0.26 %, volume per length = k_p F (0.99964 / 1.00002). (3) The band width read with Otsu from a film image (Beer–Lambert, 1 % noise) matches the closed form within 0.28 % for flat and Hertz tools at four forces each. Trap: Otsu’s threshold means a residual film of about 1/3 of h0; forgetting that is up to 36 % off. Below the minimum wiping force of 1.90 N no pixel is clean; the area of four parallel strokes is within 0.30 % at four pitches (placing the pattern on pixel centres drops a whole column at the edge, −1.7 %; it is offset by 0.3 px). (4) From before/after height maps (20 nm roughness, 1 nm noise, re-mounting) the removed depth of 240 nm is read with 1.41 nm rms and Preston’s coefficient within 0.04 % (using the outer frame as the reference is 42 % off). (5) Elastic bed: while the whole surface touches, the rms stays within 0.20 % of the exponential, Sq ratio 0.1351 vs 0.1353 from roughness. When only the peaks touch it is 19 % off. (6) MuJoCo (–full): a cylindrical tool is pressed on a plate through a wrist spring (300 N/m and 10 kN/m) and moved. The compliant wrist’s force follows the spring’s closed form (99th percentile 0.05 %), friction = μ; the stiff wrist lifts off at 19.2 mm against 19.8 mm in closed form. The band width wiped with MuJoCo’s forces matches the closed form of a stroke with varying force (median 0.55 % / 0.17 %). Band-width variation: 0.02 % compliant, 13 % stiff, 83 / 309 columns left unwiped: why one wipes with variable compliance, shown from band-width images without learning. Figures: a moving picture of parallel wiping, stroke profiles vs closed form, band width vs force, height maps and the depth reading, roughness decay on the elastic bed, area vs pitch; with –full: the force along a stroke and the bands wiped with MuJoCo’s forces. 10 gates (default, 1.9 s) plus 5 with –full (15 s with figures). Honestly: the film-removal coefficient for wiping is a placeholder (the band gate checks that the closed form and the image agree for the same coefficient). The flat pad’s uniform pressure assumes a rigid flat (the Boussinesq edge pressure is not included). The elastic bed is an assumption, and the height maps assume no lateral shift. MuJoCo’s default contact was too soft and gave forces 6–17 % low, so the contact was stiffened and an elliptic cone used. Chip formation in grinding is not modelled.*
↑ The measurement ―― 半径 5 mm の平らなパッドで、長さ 30 mm の一筆を間隔 1.2a で 4 本(持ち上げて戻る)。暗い所 = 残った膜(Beer–Lambert)、青い輪 = 工具。最後の画像で拭けた面積 961.8 mm²、閉形式 (2a + (N−1)min(s, 2a))L + Nπa² − (N−1)lens(s) = 1087.1 mm²。縁は膜が薄くなるだけで消えきらない所があり(パッドの縁は通過の弦が短い)、Otsu のしきい値がその途中に入る。 (figure labels are in Japanese; the numbers are the same)
↑ 膜 1 µm、除去係数は仮の値(平板で拭ける最小の力が 2 N になる値)。
↑ 16 × 24 mm、画素 0.1 mm。
↑ 4 本の一筆(長さ 30 mm、パッド半径 5 mm)。
↑ 板の高さの誤差 2 mm(80 mm で)。
py -3.11 examples/poc_polish_wipe_measure.py
Source: examples/poc_polish_wipe_measure.py
This run produced 8 figures in total - see them all
Ops used (notes): band_width_profile · coat_image · coat_thickness_from_image · polish_scene_mjcf · preston_coefficient_fit · preston_pressure_kernel · preston_removal_map · preston_track_profile · raster_wipe_area · removal_depth_from_heights · surface_form_remove · surface_params · surface_synth_psd · winkler_polish_run · wipe_band_width · wipe_coverage
| The physics-simulation × Fullseye series, continuing granular (repose angle, volume and Beverloo discharge of a heap). Prior work on powder weighing (Kadokawa, Hamaya, Tanaka, IROS 2023) observes only the scale’s mass. Here side-view images give the amount scooped into a spoon and the flow rate when tilting to pour. New module scoop, 15 ops plus 2 mujoco facades. (1) Scooped amount: a spherical-cap bowl (rim radius a, depth h) levelled holds V = πh(3a² + h²)/6 (1.4e-5 from 300³ voxels); a heaped spoon adds a cone at the repose angle above the rim (matching granular’s heap_volume_cone). Side-view volume is read in Pappus form π Σ | x − x_axis | c: linear in coverage, so a flat powder surface crossing a row midway does not bias it (trap: summing discs with the row width as diameter undercounts by f², −1.5 %). A metal bowl hides everything below the rim, so the levelled closed form plus the solid of revolution above the rim is used; an empty region above the rim raises ValueError, since levelled vs short cannot be told from the side. An elongated heap (elliptic cone 1.6 : 1) is −0.01 % from the voxel truth with the elliptic sum of two orthogonal views; one view as a solid of revolution is −37.5 % / +60.0 %. (2) Grain count (MuJoCo, –full): the bowl is built from 118 thin plates and rigid balls (radius 2 mm) are stacked inside and released (dropping them from above bounced out all but 172 of 500). Volume from two views × a packing fraction calibrated once (ν = 0.539, smaller than the true packing because the silhouette is the outer envelope of the grains) gives the other five counts within +3.2 / +0.1 / −0.5 / −3.8 / −4.3 %. Read as a metal bowl, 600 balls whose heap reaches the rim are +1.0 %; 330 balls just starting to heap are +9.3 % → flagged rim_full=False. At radius 3.5 mm (rim radius / grain diameter 4.3) the same calibration is −9.5 % → the rule scoop_image_limit sends a/d < 5 to the scale. (3) Pour rate: speed by PIV (median of all frame pairs of pivops.piv_cross_correlate), line density by inverting the time-averaged coverage with the Boolean model c = 1 − exp(−nπr²) and integrating across, rate = λ v. On synthetic streams the speed is within 0.82 % and the rate 0.98–1.03× the realised crossings; the naive c/(πr²) is 0.60–0.73×. Tilting an open-lipped trough (665 balls) at 15°/s in MuJoCo, the image rate is 0.98–1.04× the balls crossing a line (five intervals at 6–26°, 160–482 balls/s; naive 0.88–0.95), speed within 1.6 % of free fall √(v₀² + 2gs), and the time-integrated amount −5.8 % of the balls that left. The rate is 0.42× a Beverloo estimate with the lip layer as an equivalent diameter (order of magnitude only). (4) Dependence on tilt (own derivation): in a container with no lip wall the front is a repose slope from the start, so the retained section is A(θ) = ∫₀ᴸ min(h₀, x tan(φ − θ)) dx and powder spills from θ = 0⁺. Fitting φ and depth to MuJoCo’s poured fraction, the lip wedge has RMS 0.023 and a container with a lip wall (granular’s lip=”wall”, initially full to the lip) 0.050; 2 % has left at 1.3° < its θ_c of 5.4°. On this derivation granular’s wedge slope was corrected from the small-angle tan φ − tan θ (RMS 0.038) to the exact tan(φ − θ), and the lip-wedge formula now lives in one place in granular (the walled container fits worse once exact: the approximation’s error happened to cancel the error of assuming a lip wall). Reading what remains from the image cross-section (minus grain radius × free-surface length) lags the count by up to 0.09. Figures: side views of scoops, two views of an elliptic heap, a GIF of a synthetic stream, speed vs free fall, Boolean model vs naive, the two tilt models, image or scale; with –full: MuJoCo bowls, a pouring GIF, poured fraction, image rate vs count, a table against references. 15 gates (default, 0.7 s) plus 10 with –full (71 s with figures). Honestly: side-view volume assumes axial symmetry or elliptic sections. Volume → count needs one packing calibration (ν rises with amount, ±4 %). The Boolean model assumes independent grain positions and fails in dense flow near the lip. The tilt comparison fits φ and depth (not independent), and neither model has the flat back layer actually flowing and thinning (primary sources not read). MuJoCo uses soft contacts of rigid, non-cohesive, monodisperse balls; µm powders are not covered. |
| *↑ Measuring Scooping and Pouring of Powder from Images — the Scooped Amount from Side-View Silhouettes, the Pour Rate from PIV (particle image velocimetry) Speed × a Boolean-Model Line Density, within 5 % of MuJoCo’s Ball Counts and Line Crossings ―― The physics-simulation × Fullseye series, continuing granular (repose angle, volume and Beverloo discharge of a heap). Prior work on powder weighing (Kadokawa, Hamaya, Tanaka, IROS 2023) observes only the scale’s mass. Here side-view images give the amount scooped into a spoon and the flow rate when tilting to pour. New module scoop, 15 ops plus 2 mujoco facades. (1) Scooped amount: a spherical-cap bowl (rim radius a, depth h) levelled holds V = πh(3a² + h²)/6 (1.4e-5 from 300³ voxels); a heaped spoon adds a cone at the repose angle above the rim (matching granular’s heap_volume_cone). Side-view volume is read in Pappus form π Σ | x − x_axis | c: linear in coverage, so a flat powder surface crossing a row midway does not bias it (trap: summing discs with the row width as diameter undercounts by f², −1.5 %). A metal bowl hides everything below the rim, so the levelled closed form plus the solid of revolution above the rim is used; an empty region above the rim raises ValueError, since levelled vs short cannot be told from the side. An elongated heap (elliptic cone 1.6 : 1) is −0.01 % from the voxel truth with the elliptic sum of two orthogonal views; one view as a solid of revolution is −37.5 % / +60.0 %. (2) Grain count (MuJoCo, –full): the bowl is built from 118 thin plates and rigid balls (radius 2 mm) are stacked inside and released (dropping them from above bounced out all but 172 of 500). Volume from two views × a packing fraction calibrated once (ν = 0.539, smaller than the true packing because the silhouette is the outer envelope of the grains) gives the other five counts within +3.2 / +0.1 / −0.5 / −3.8 / −4.3 %. Read as a metal bowl, 600 balls whose heap reaches the rim are +1.0 %; 330 balls just starting to heap are +9.3 % → flagged rim_full=False. At radius 3.5 mm (rim radius / grain diameter 4.3) the same calibration is −9.5 % → the rule scoop_image_limit sends a/d < 5 to the scale. (3) Pour rate: speed by PIV (median of all frame pairs of pivops.piv_cross_correlate), line density by inverting the time-averaged coverage with the Boolean model c = 1 − exp(−nπr²) and integrating across, rate = λ v. On synthetic streams the speed is within 0.82 % and the rate 0.98–1.03× the realised crossings; the naive c/(πr²) is 0.60–0.73×. Tilting an open-lipped trough (665 balls) at 15°/s in MuJoCo, the image rate is 0.98–1.04× the balls crossing a line (five intervals at 6–26°, 160–482 balls/s; naive 0.88–0.95), speed within 1.6 % of free fall √(v₀² + 2gs), and the time-integrated amount −5.8 % of the balls that left. The rate is 0.42× a Beverloo estimate with the lip layer as an equivalent diameter (order of magnitude only). (4) Dependence on tilt (own derivation): in a container with no lip wall the front is a repose slope from the start, so the retained section is A(θ) = ∫₀ᴸ min(h₀, x tan(φ − θ)) dx and powder spills from θ = 0⁺. Fitting φ and depth to MuJoCo’s poured fraction, the lip wedge has RMS 0.023 and a container with a lip wall (granular’s lip=”wall”, initially full to the lip) 0.050; 2 % has left at 1.3° < its θ_c of 5.4°. On this derivation granular’s wedge slope was corrected from the small-angle tan φ − tan θ (RMS 0.038) to the exact tan(φ − θ), and the lip-wedge formula now lives in one place in granular (the walled container fits worse once exact: the approximation’s error happened to cancel the error of assuming a lip wall). Reading what remains from the image cross-section (minus grain radius × free-surface length) lags the count by up to 0.09. Figures: side views of scoops, two views of an elliptic heap, a GIF of a synthetic stream, speed vs free fall, Boolean model vs naive, the two tilt models, image or scale; with –full: MuJoCo bowls, a pouring GIF, poured fraction, image rate vs count, a table against references. 15 gates (default, 0.7 s) plus 10 with –full (71 s with figures). Honestly: side-view volume assumes axial symmetry or elliptic sections. Volume → count needs one packing calibration (ν rises with amount, ±4 %). The Boolean model assumes independent grain positions and fails in dense flow near the lip. The tilt comparison fits φ and depth (not independent), and neither model has the flat back layer actually flowing and thinning (primary sources not read). MuJoCo uses soft contacts of rigid, non-cohesive, monodisperse balls; µm powders are not covered.* |
Terms: PIV — A flow-measurement method that estimates a velocity field from the displacement of particle patterns between images separated by a short time.
↑ The measurement ―― 縦長の盛り(楕円錐 1.6 : 1)を直交する 2 方向から見た像(等倍)。2 方向の楕円の和は体素の真値に -0.01 %、片方だけの回転体は -37 % / +60 % 外れる。 (figure labels are in Japanese; the numbers are the same)
↑ PIV(全コマ対の中央値)の速さと自由落下の閉形式。
↑ 口に壁の無い器(前面が初めから安息角の斜面)は θ = 0⁺ からこぼれ、口に縁のある器(granular の lip=”wall”、初めは口まで平らに満ちている)は θ_c まで 1 粒も出ない。
↑ 球冠の椀(青 = 内面の閉形式、縁 30 mm、深さ 12 mm、薄板 118 枚)にすくった剛体球(半径 2 mm)150 / 330 / 600 個(陰影つき正射影、0.25 mm/px、等倍)。
↑ 流れの像から読んだ流量(3 高さの平均)と MuJoCo で線を横切った数。
↑ The animation ―― 合成の流れ(3000 個/s、半径 1 mm、幅 8 mm、0.5 mm/px を 2 倍の最近傍、1.5 ms/コマ)。破線 = 流量を読む 3 つの帯。
↑ The animation ―― 口の開いた樋(青 = 床と奥壁、床 80 mm、幅 24 mm、滑らかな側壁)を口の縁を軸に 15 度/s で傾ける(剛体球 665 個、0.5 mm/px、陰影つき正射影)。数は傾け始めに器にあった球のうち口を越えた数。
py -3.11 examples/poc_powder_scoop_pour.py
Source: examples/poc_powder_scoop_pour.py
This run produced 12 figures in total - see them all
Ops used (notes): beverloo_rate · difference · heap_volume_cone · pour_scene_mjcf · revolution_volume_side · scoop_count · scoop_image_limit · scoop_scene_mjcf · scoop_synth_side · scoop_volume_read · spheres_render_shaded · spheres_to_silhouette · spoon_bowl_volume · stream_flux_read · stream_synth · text_box · tilt_pour_rate · tilt_wedge_retained · tilted_surface_read · two_view_volume
The physics-simulation × Fullseye series, the third powder exhibit (after granular and scoop). The subject is research in which a robot grinds powder in a mortar and monitors it by acoustic emission (AE). Its published data (Zenodo concept DOI 10.5281/zenodo.18064323, CC BY 4.0) — laser-diffraction size distributions (NaCl / citric acid / monosodium glutamate × three independent runs × seven times from 3 to 25 min, plus before grinding, 60 min and by hand) and six raw AE waveforms — are the outside truth. The data are not in the repo (117 distributions and 6 AE files are extracted and passed via the environment variable FULLSEYE_GRIND_DATA; CI without them runs only the 12 numpy gates). New module grind, 13 ops, no facade. (1) The D50 definition: reading how the instrument CSV pairs bin edges with volume rows, the header’s Dx(50) equals log-diameter linear interpolation of the cumulative at the edges for all 117 files (max difference 3.2e-15). One stage of the published analysis code puts the cumulative at the lower edge and comes out one bin (ratio 1.136) small: −11.80 to −11.98 %. (2) Fitting comminution laws (Reddy’s eqs. (1) and (5)–(7), dE = −C dx/xⁿ: Kick n = 1, Bond 1.5, Rittinger 2) to net grinding time (rms of log D50, 21 points): NaCl Rittinger 0.050, citric acid Bond 0.100, MSG Bond 0.156. Kick is never best. But a Monte Carlo rebuilding the three laws at each material’s times, shrinkage and residual shows how often the true law wins: citric acid 92–100 % (20× shrinkage), MSG 70–92 %, NaCl 58–78 % (2.7×) — NaCl’s “Rittinger is best” cannot be trusted. Extrapolating to before grinding (a measurement not used in the fit): −6 / −18 / −11 % with the best law; with n free it worsens to +34 / +57 % (overfitting). (3) Spread over three independent runs (cv of D50, rms over seven times): NaCl 5.2 %, citric acid 6.5 %, MSG 13.2 %. Repeat measurements of the same powder have cv 0.8–17.1 %, not necessarily smaller than independent runs. At 25 min every pair of materials is more than 12 standard errors apart. (4) The apparent first-order rate of the oversize R(200 µm) (Deniz 2004, after Austin) is 0.17–0.20 /min, slower in the second half (departing from first order). (5) AE band power (0.1–1 MHz) matches the published analysis code run on the same six files with a difference of 0.0, and the band integral of acoustics.stft’s density via Parseval at 0.982–1.052. From 3 to 25 min: NaCl 185 → 31 mV², citric acid 806 → 7.1, MSG 2209 → 184, all in the same direction as D50. (6) At 60 min citric acid is coarser (39.9 µm) than at 25 min (11.4 µm) — agglomeration — and no monotonically fining law can represent it. Figures: synthetic particles with volume vs number basis, law identifiability (synthetic), D50 curves of three materials with three laws, a GIF of the distribution fining, AE spectrograms (start and end on the same colour scale), AE band power vs D50, band power per frame, first-order oversize rate, a table of where the laws break at 60 min, identifiability on the real design. 12 gates (numpy, 0.6 s) plus 10 with data; –full runs 10× the Monte Carlo (25 s with figures). Honestly: energy ∝ net time is an assumption (used only to compare laws). AE has two points per material, so monotonicity and exponents are two-point ratios and no correlation strength can be claimed. The paper’s text, Bond 1952 and Austin’s original were not read. The 60 min and pre-grinding measurements used different instrument settings. D50 from images is gated on synthetic images only.
↑ Measuring Mortar Grinding — D50 of the Size Distribution, Comminution Laws, Spread over Three Independent Runs and AE Band Power, against Published Laser-Diffraction Measurements; a Different D50 Definition Is a One-Bin, 12 % Bias ―― The physics-simulation × Fullseye series, the third powder exhibit (after granular and scoop). The subject is research in which a robot grinds powder in a mortar and monitors it by acoustic emission (AE). Its published data (Zenodo concept DOI 10.5281/zenodo.18064323, CC BY 4.0) — laser-diffraction size distributions (NaCl / citric acid / monosodium glutamate × three independent runs × seven times from 3 to 25 min, plus before grinding, 60 min and by hand) and six raw AE waveforms — are the outside truth. The data are not in the repo (117 distributions and 6 AE files are extracted and passed via the environment variable FULLSEYE_GRIND_DATA; CI without them runs only the 12 numpy gates). New module grind, 13 ops, no facade. (1) The D50 definition: reading how the instrument CSV pairs bin edges with volume rows, the header’s Dx(50) equals log-diameter linear interpolation of the cumulative at the edges for all 117 files (max difference 3.2e-15). One stage of the published analysis code puts the cumulative at the lower edge and comes out one bin (ratio 1.136) small: −11.80 to −11.98 %. (2) Fitting comminution laws (Reddy’s eqs. (1) and (5)–(7), dE = −C dx/xⁿ: Kick n = 1, Bond 1.5, Rittinger 2) to net grinding time (rms of log D50, 21 points): NaCl Rittinger 0.050, citric acid Bond 0.100, MSG Bond 0.156. Kick is never best. But a Monte Carlo rebuilding the three laws at each material’s times, shrinkage and residual shows how often the true law wins: citric acid 92–100 % (20× shrinkage), MSG 70–92 %, NaCl 58–78 % (2.7×) — NaCl’s “Rittinger is best” cannot be trusted. Extrapolating to before grinding (a measurement not used in the fit): −6 / −18 / −11 % with the best law; with n free it worsens to +34 / +57 % (overfitting). (3) Spread over three independent runs (cv of D50, rms over seven times): NaCl 5.2 %, citric acid 6.5 %, MSG 13.2 %. Repeat measurements of the same powder have cv 0.8–17.1 %, not necessarily smaller than independent runs. At 25 min every pair of materials is more than 12 standard errors apart. (4) The apparent first-order rate of the oversize R(200 µm) (Deniz 2004, after Austin) is 0.17–0.20 /min, slower in the second half (departing from first order). (5) AE band power (0.1–1 MHz) matches the published analysis code run on the same six files with a difference of 0.0, and the band integral of acoustics.stft’s density via Parseval at 0.982–1.052. From 3 to 25 min: NaCl 185 → 31 mV², citric acid 806 → 7.1, MSG 2209 → 184, all in the same direction as D50. (6) At 60 min citric acid is coarser (39.9 µm) than at 25 min (11.4 µm) — agglomeration — and no monotonically fining law can represent it. Figures: synthetic particles with volume vs number basis, law identifiability (synthetic), D50 curves of three materials with three laws, a GIF of the distribution fining, AE spectrograms (start and end on the same colour scale), AE band power vs D50, band power per frame, first-order oversize rate, a table of where the laws break at 60 min, identifiability on the real design. 12 gates (numpy, 0.6 s) plus 10 with data; –full runs 10× the Monte Carlo (25 s with figures). Honestly: energy ∝ net time is an assumption (used only to compare laws). AE has two points per material, so monotonicity and exponents are two-point ratios and no correlation strength can be claimed. The paper’s text, Bond 1952 and Austin’s original were not read. The 60 min and pre-grinding measurements used different instrument settings. D50 from images is gated on synthetic images only.
↑ The measurement ―― 同じ画像の同じ粒子でも、体積基準の D50 は個数基準の 1.39 倍。レーザー回折は体積基準。 (figure labels are in Japanese; the numbers are the same)
↑ 縮みが小さいと、雑音 5 % でも 3 則の区別がつかない(200 回ずつ)。
↑ citric acid の D50(独立 3 回)と 3 則の当てはめ。
↑ 各材料 2 点しか取っていない(データ量の上限)ので、傾き α は 2 点の比。
↑ この回の図
↑ The animation ―― 1st の試行の累積粒度分布が、粉砕前 → 3 → 25 min で左(細かい側)へ動く。
py -3.11 examples/poc_powder_grinding_ae.py
Source: examples/poc_powder_grinding_ae.py
This run produced 13 figures in total - see them all
Ops used (notes): ae_band_power · ae_read_csv · ae_size_correspondence · blob_label · blob_overlay · breakage_first_order_fit · comminution_energy · comminution_law_fit · particle_image_d50 · particle_image_synth · particle_size_dx · particle_size_oversize · particle_size_read · particle_size_synth · replicate_compare
The sequel to grind (measuring mortar grinding). Take one dose from a well-mixed powder and its content varies just because the number of drug particles fluctuates as Poisson. From the compound Poisson process we derive CV² = (πρ/6)·D63³/D (D63 = cube root of the ratio of the 6th to the 3rd number-weighted moment); from a volume-basis size distribution the identity D63³ = E_v[d³] gives it without assuming a shape. Fitting a comminution law to D63 inverts to the grinding time at which the CV reaches a target (6.25 %, equivalent to an acceptance value of 15, or a stage-1 pass probability). Closed form vs Monte Carlo (drawing diameters and summing, no formula): max difference / standard error 1.72 over 8 conditions; identities to 1e-16. ★The pilot run’s “CV from photos is consistently −6 % at four sizes” was an artefact: the four sizes were the same 12 images in pixel units (gate 4). With independent batches the main cause is dropping particles that touch the frame edge (0.956); Miles–Lantuéjoul weights plus a lognormal fit give 1.001 and halve the spread (±0.102 → ±0.060). A powder at CV 6.25 % passes stage 1 only about half the time (chi-square 0.563, 0.501 with the mean-offset term). On published laser-diffraction data (outside the repo, FULLSEYE_GRIND_DATA) a lognormal from D16/D50/D84 overestimates the CV by a median 1,336× (bimodal powders), so it is unusable; reaching CV 6.25 % at 5 mg takes NaCl 24 min, citric acid 28 min and MSG 31 min (the last two extrapolated), and 0.1 mg needs 96–206 min. Honestly: the CV is only the particle-count floor, the acceptance numbers come from secondary sources, and fitting the law to D63 is an assumption. 13 gates (11 on CI without data, 2.3 s); default 1,000 Monte Carlo draws and 30 image batches, –full 8,000 and 200.
↑ How Long to Grind for a Uniform Dose — Content Uniformity from Particle Size by Closed Form and Monte Carlo, Inverted through Comminution Laws; the Low CV from Photos Was the Same 12 Images at Different Scales ―― The sequel to grind (measuring mortar grinding). Take one dose from a well-mixed powder and its content varies just because the number of drug particles fluctuates as Poisson. From the compound Poisson process we derive CV² = (πρ/6)·D63³/D (D63 = cube root of the ratio of the 6th to the 3rd number-weighted moment); from a volume-basis size distribution the identity D63³ = E_v[d³] gives it without assuming a shape. Fitting a comminution law to D63 inverts to the grinding time at which the CV reaches a target (6.25 %, equivalent to an acceptance value of 15, or a stage-1 pass probability). Closed form vs Monte Carlo (drawing diameters and summing, no formula): max difference / standard error 1.72 over 8 conditions; identities to 1e-16. ★The pilot run’s “CV from photos is consistently −6 % at four sizes” was an artefact: the four sizes were the same 12 images in pixel units (gate 4). With independent batches the main cause is dropping particles that touch the frame edge (0.956); Miles–Lantuéjoul weights plus a lognormal fit give 1.001 and halve the spread (±0.102 → ±0.060). A powder at CV 6.25 % passes stage 1 only about half the time (chi-square 0.563, 0.501 with the mean-offset term). On published laser-diffraction data (outside the repo, FULLSEYE_GRIND_DATA) a lognormal from D16/D50/D84 overestimates the CV by a median 1,336× (bimodal powders), so it is unusable; reaching CV 6.25 % at 5 mg takes NaCl 24 min, citric acid 28 min and MSG 31 min (the last two extrapolated), and 0.1 mg needs 96–206 min. Honestly: the CV is only the particle-count floor, the acceptance numbers come from secondary sources, and fitting the law to D63 is an assumption. 13 gates (11 on CI without data, 2.3 s); default 1,000 Monte Carlo draws and 30 image batches, –full 8,000 and 200.
↑ The measurement ―― レーザー回折の粒度分布から形を仮定せずに出した CV(粒の数の揺らぎだけ = 下限)。3 則の必要時間 24〜24 min。目標が測った範囲の近くなので則はほぼ同じ答えを返す。0.1 mg(100〜206 min)のような遠い外挿では則で大きく開く。 (figure labels are in Japanese; the numbers are the same)
↑ レーザー回折の粒度分布から形を仮定せずに出した CV(粒の数の揺らぎだけ = 下限)。
↑ レーザー回折の粒度分布から形を仮定せずに出した CV(粒の数の揺らぎだけ = 下限)。
↑ 同じ粉を挽く時間だけ変えて 5 mg を 10 個取った含量(粒の数の揺らぎだけ、正規近似の 1 回の模擬)。
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_dose_uniformity_from_grinding/06_grinding_time_by_dose.png)
↑ 最良の則で、CV 6.25 % に要る粉砕時間 [min]。
py -3.11 examples/poc_dose_uniformity_from_grinding.py
Source: examples/poc_dose_uniformity_from_grinding.py
This run produced 7 figures in total - see them all
Ops used (notes): comminution_energy · dose_cv_from_sizes · dose_cv_lognormal · grind_time_for_dose_cv · particle_image_d50 · particle_image_synth · particle_size_read · particle_size_synth
The chain of cutting (measuring food cutting from images) and pegtactile (contact wrench from two fingertip membranes). Pinch the spine of a kitchen knife between two pads and recover, from the membrane images alone, the push V, the pull H and the moment M_x, then read the blade’s contact position Ly = (M_x + Lz·H)/V, the slice/push ratio ξ̂ = H/V and the toughness R. ★What we found and fixed: pegtactile.pad_tactile_read reads the pad torsion with the no-slip (Reissner–Sagoci) relation, but torsion of a Hertz contact always slips from the edge, so it over-reads M — on partial-slip images ×1.328 at half the full-slip torque and ×2.022 at 0.85 (within 0.4 % of the numerical prediction). Splitting the contact into 64 rings and solving partial slip with the Cerruti-kernel influence matrix (both limits are closed forms: β/β_RS 0.9986 as c → a, the full-slip torque +0.11 % as c → 0) corrects it to within 0.2 %. A MuJoCo cut (4 frames for the default gates, 14 for the figures) → from the pad images alone R̂ 59.86 J/m² (set 60), ξ̂ 0.0701 (tan θ 0.0699), contact position error median 0.01 mm and max 0.48 mm (6.09 mm if the no-slip reading is kept). How long a handle can be held: at a 4 N grip the knife cannot be read once the blade contact is more than 6.0 mm from the grasp (13.2 mm at 8 N, 31.3 mm at 16 N), and outside that range the result carries readable = False. Honestly: the membrane synthesis and the correction use the same numerical solution (whether a real gel behaves like a half-space in partial slip is untested), shear and torsion are superposed, V is the formula written into MuJoCo’s friction loss, and H was made as ξV. 13 gates (3.0 s; without mujoco a closed-form cut stands in); –full runs all 79 frames, 96 rings and 17 positions.
↑ Holding and Cutting with a Knife by Fingertip Visuotactile Sensing Alone — Toughness, Blade Contact Position and How Long a Handle Can Be Held, without a Wrist Force Sensor; the Existing Reader’s Torsion Over-read (+33 % at Half Slip) Fixed by a Numerical Partial-Slip Solution ―― The chain of cutting (measuring food cutting from images) and pegtactile (contact wrench from two fingertip membranes). Pinch the spine of a kitchen knife between two pads and recover, from the membrane images alone, the push V, the pull H and the moment M_x, then read the blade’s contact position Ly = (M_x + Lz·H)/V, the slice/push ratio ξ̂ = H/V and the toughness R. ★What we found and fixed: pegtactile.pad_tactile_read reads the pad torsion with the no-slip (Reissner–Sagoci) relation, but torsion of a Hertz contact always slips from the edge, so it over-reads M — on partial-slip images ×1.328 at half the full-slip torque and ×2.022 at 0.85 (within 0.4 % of the numerical prediction). Splitting the contact into 64 rings and solving partial slip with the Cerruti-kernel influence matrix (both limits are closed forms: β/β_RS 0.9986 as c → a, the full-slip torque +0.11 % as c → 0) corrects it to within 0.2 %. A MuJoCo cut (4 frames for the default gates, 14 for the figures) → from the pad images alone R̂ 59.86 J/m² (set 60), ξ̂ 0.0701 (tan θ 0.0699), contact position error median 0.01 mm and max 0.48 mm (6.09 mm if the no-slip reading is kept). How long a handle can be held: at a 4 N grip the knife cannot be read once the blade contact is more than 6.0 mm from the grasp (13.2 mm at 8 N, 31.3 mm at 16 N), and outside that range the result carries readable = False. Honestly: the membrane synthesis and the correction use the same numerical solution (whether a real gel behaves like a half-space in partial slip is untested), shear and torsion are superposed, V is the formula written into MuJoCo’s friction loss, and H was made as ξV. 13 gates (3.0 s; without mujoco a closed-form cut stands in); –full runs all 79 frames, 96 rings and 17 positions.
↑ The measurement ―― 押し 2 N の切断で、刃の当たり位置が指から離れるほどパッドのねじりが増え、固着円が縮む。把持力 4 N では -3.5〜6.0 mm の外で読めなくなり、-4.5〜7.0 mm の外で全滑り。把持力を 4 倍にすると範囲は約 6.3 倍(全滑りのトルク ∝ P a ∝ P^{4/3})。 (figure labels are in Japanese; the numbers are the same)
↑ 無滑りの関係でねじりを読むと、指から離れるほど当たり位置を遠くに読む(部分滑りでねじれ角が大きい)。
↑ 刃が食材の左端から切れ始め、切っている幅の中点(当たり位置)が右へ動く。
↑ パッドのねじれ(周方向の変位 ÷ 半径)。
↑ The animation ―― MuJoCo の柔らかい手首で刃を押し下げる(上)。下は包丁の背を挟む 2 枚のパッドの膜の像 —— この像だけで力と当たり位置を読む。
py -3.11 examples/poc_knife_tactile_toughness.py
Source: examples/poc_knife_tactile_toughness.py
This run produced 6 figures in total - see them all
Ops used (notes): combined_modulus · cutting_episode_synth · cutting_scene · food_cut_width · hertz_sphere · knife_load_from_pads · membrane_indent_sphere · membrane_render_markers · membrane_render_rgb · pad_context · pad_marker_displacement · pad_params · pad_tactile_read · peg_wrench_to_pad_loads · rigid_rotation_fit · torsion_partial_slip · toughness_from_pads
Separate how much of a number comes from the parts and how much from the act of measuring, then combine the components of one measurement into a reportable uncertainty - scored entirely from outside the picture. The analysis-of-variance decomposition closes algebraically (1e-16); repeatability equals the mean of the per-cell sample variance numpy computes independently; the published worked example is reproduced to 1.5e-06 with contribution percentages 3.4/4.4/7.8/92.2 exactly. Keeping or pooling the interaction moves EV by 7.3 %, so the model actually used is reported; a negative variance component appears in 24 of 40 runs when the true component is zero and is declared, not hidden. Ignoring correlation errs in both directions on the same data (0.0702 -> 0.1945, a 2.8x overestimate, while reactance goes the other way). The effective degrees of freedom are truncated immediately before the t lookup (16.64 -> 16, k = 2.1199). At a stationary point the propagation law returns u = 0 and says so through structure rather than silently; Monte Carlo returns [0, 150]e-6 there. A sum of four rectangular distributions has an exact Irwin-Hall interval of -3.879407, against which the normal approximation is structurally 0.040521 too wide.
↑ How Much of That Number Is Your Measuring - Gauge R&R (gauge repeatability and reproducibility) and Measurement Uncertainty ―― Separate how much of a number comes from the parts and how much from the act of measuring, then combine the components of one measurement into a reportable uncertainty - scored entirely from outside the picture. The analysis-of-variance decomposition closes algebraically (1e-16); repeatability equals the mean of the per-cell sample variance numpy computes independently; the published worked example is reproduced to 1.5e-06 with contribution percentages 3.4/4.4/7.8/92.2 exactly. Keeping or pooling the interaction moves EV by 7.3 %, so the model actually used is reported; a negative variance component appears in 24 of 40 runs when the true component is zero and is declared, not hidden. Ignoring correlation errs in both directions on the same data (0.0702 -> 0.1945, a 2.8x overestimate, while reactance goes the other way). The effective degrees of freedom are truncated immediately before the t lookup (16.64 -> 16, k = 2.1199). At a stationary point the propagation law returns u = 0 and says so through structure rather than silently; Monte Carlo returns [0, 150]e-6 there. A sum of four rectangular distributions has an exact Irwin-Hall interval of -3.879407, against which the normal approximation is structurally 0.040521 too wide.
Terms: Gauge R&R — A measurement-system analysis in which multiple appraisers repeatedly measure parts to separate equipment repeatability from appraiser reproducibility.
↑ The measurement ―― 測定の行為(GRR)が総変動に占めるのは 7.8 %、部品どうしの差が 92.2 %。どちらも公表値と一致する(最大差 1.5e-06)。 (figure labels are in Japanese; the numbers are the same)
↑ 各升目(部品 × 測定者)で 3 回測った値の幅。
↑ 交互作用が無いところ(左端)では畳むほうが真値 0.30 に近く、あるところでは畳むと交互作用を誤差に混ぜてしまうので上へ外れる。
↑ 電圧と電流の相関だけを振った(他の 2 つは実測値で固定)。
↑ 校正証明書に「±a」とだけ書いてあるとき、それを標準不確かさへ直す除数は形で決まります —— 矩形なら a/√3、三角なら a/√6、両端に寄る U 字(温度の上下動など)なら a/√2、「95 % の幅」と書いてあるなら 1.959964 で割る。
↑ The animation ―― 評価点 x₁ を 0 から 0.026 へ動かしたもの。★左端では真の分布が原点に肩を持つ指数(u²χ²₂)で、伝播則は感度 c = 2x₁ が 0 になるため区間が1 点に潰れる。少し動かすと今度は区間が負の損失へ張り出す(物理的にありえない)。さらに離れると真の分布が正規に近づき、両者はようやく重なる —— 壊れ方は連続ではなく、3 つの段階がある。
py -3.11 examples/poc_measurement_system_analysis.py
Source: examples/poc_measurement_system_analysis.py
This run produced 22 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · grid_lines · gum_expanded · gum_monte_carlo · gum_propagate · gum_standard_uncertainty · gum_validate · legend_box · msa_anova_table · msa_attribute_agreement · msa_bias_linearity · msa_gauge_rr · nice_ticks · plot_series · text_box · ticks
V-groove scratches 2-300 µm wide on hairline-finished metal, rendered with the illumination physics, imaged by the same camera and processed by the same chain under three lightings; the truth is the scratch width that was put in. The design formula says the smallest resolvable size is 202 µm, yet with a 70° ring - where the flank mirrors the light into the camera - a90/95 is 8.9 µm, under a dome 27.1 µm, and with a 30° ring 105 of 120 scratches stay below the recording threshold and the a90/95 lies beyond the sizes tested (flagged as extrapolation). Re-rendering 200 scratches at the fitted a90 gives a measured detection rate of 0.910 / 0.845 (band 0.9 +/- 0.064); the hit/miss GLM agrees with the â-vs-a a90 to 0.15 / 0.18 SE; ignoring censoring flattens the slope by up to 4.7 SE; and unflawed sites given random sizes are refused a curve.
↑ The Resolution Formula Does Not Tell You What You Will Find - Probability of Detection per Lighting (MIL-HDBK-1823A) ―― V-groove scratches 2-300 µm wide on hairline-finished metal, rendered with the illumination physics, imaged by the same camera and processed by the same chain under three lightings; the truth is the scratch width that was put in. The design formula says the smallest resolvable size is 202 µm, yet with a 70° ring - where the flank mirrors the light into the camera - a90/95 is 8.9 µm, under a dome 27.1 µm, and with a 30° ring 105 of 120 scratches stay below the recording threshold and the a90/95 lies beyond the sizes tested (flagged as extrapolation). Re-rendering 200 scratches at the fitted a90 gives a measured detection rate of 0.910 / 0.845 (band 0.9 +/- 0.064); the hit/miss GLM agrees with the â-vs-a a90 to 0.15 / 0.18 SE; ignoring censoring flattens the slope by up to 4.7 SE; and unflawed sites given random sizes are refused a curve.
Terms: POD — A curve giving the probability that a specified inspection procedure detects a flaw as a function of flaw size or another explanatory variable.; a90/95 — The flaw size at which the lower one-sided 95% confidence bound on the POD reaches 90%.
↑ The measurement ―― 記録閾値より下は左打ち切り、応答の天井の 8 割より上は右打ち切りとして尤度に入る(照明ごとに天井が違う)。 (figure labels are in Japanese; the numbers are the same)
↑ 仰角 70° の a90/95 = 8.9 µm、ドーム 27.1 µm、設計の式の限界 202 µm。
↑ 閾値を上げれば誤検出は減るが、90 % 見つかる大きさは伸びる(1823A 図 G-28)。
↑ 真値は描画に入れた傷の幅。
py -3.11 examples/poc_pod_lighting.py
Source: examples/poc_pod_lighting.py
This run produced 5 figures in total - see them all
Ops used (notes): light_source · pod_ahat_fit · pod_decision_tradeoff · pod_false_call · pod_from_ahat · pod_hitmiss_fit
| A sequel to the weld-bead scan: the same laser line, re-rendered by coherent imaging (rough-surface phase, observation pupil, | E | ^2) instead of a smooth band plus additive noise; the truth is the height put into the rendering. The three-point log-parabola that beat the brightest-pixel estimator 8.6-fold under additive noise falls to the brightest pixel’s error under speckle (12.7 vs 13.1 µm) and is twice as bad as the centroid (6.4 µm, 0.93 of Dorsch’s floor C·λ/(2π sin u sin θ) = 6.9 µm) - three samples around the peak follow the brightest grain. Averaging 100 frames of a static part leaves the error at 6.91 -> 6.79 µm; moving the part gives 1/sqrt(N) (0.099). The additive-noise model wrongly promises that averaging helps. Errors over aperture and triangulation angle follow the formula with slopes -0.94 / -0.99 at 0.84-0.90 of its value. Four-times-larger pixels blunt the visible contrast from 1.02 to 0.80 yet move the centroid by 1.6e-13 µm. |
| *↑ Speckle Sets the Error Floor of a Laser Sheet-of-Light - It Does Not Average Away, and the Estimator Ranking Flips ―― A sequel to the weld-bead scan: the same laser line, re-rendered by coherent imaging (rough-surface phase, observation pupil, | E | ^2) instead of a smooth band plus additive noise; the truth is the height put into the rendering. The three-point log-parabola that beat the brightest-pixel estimator 8.6-fold under additive noise falls to the brightest pixel’s error under speckle (12.7 vs 13.1 µm) and is twice as bad as the centroid (6.4 µm, 0.93 of Dorsch’s floor C·λ/(2π sin u sin θ) = 6.9 µm) - three samples around the peak follow the brightest grain. Averaging 100 frames of a static part leaves the error at 6.91 -> 6.79 µm; moving the part gives 1/sqrt(N) (0.099). The additive-noise model wrongly promises that averaging helps. Errors over aperture and triangulation angle follow the formula with slopes -0.94 / -0.99 at 0.84-0.90 of its value. Four-times-larger pixels blunt the visible contrast from 1.02 to 0.80 yet move the centroid by 1.6e-13 µm.* |
↑ The measurement ―― 列ごとの高さの RMS 誤差。加法雑音で最良の 3 点当てはめは、スペックルではいちばん明るい粒を追う。光条全体を平均する重心だけが床に着く。 (figure labels are in Japanese; the numbers are the same)
↑ 部品が止まっていれば 100 枚平均しても誤差はそのまま。
↑ 光条の幅と同じ長さ(48 µm)の束ごとの重心。
↑ θ = 15°〜60°。
py -3.11 examples/poc_laser_line_speckle_floor.py
Source: examples/poc_laser_line_speckle_floor.py
This run produced 5 figures in total - see them all
Ops used (notes): coherent_image · laser_line_coherent · speckle_field · speckle_reduction · speckle_statistics · triangulation_speckle_limit
A white Lambertian part (albedo 0.85, no gloss, no metal) with three 1 mm grooves - a 90 deg inner corner, a 60 deg V-groove and a pocket with rounded corners - measured by fringe projection; the truth is a light-transport matrix (2-D radiosity from Hottel’s crossed-strings form factors) and a direct-only rendering with zero bounces. Shifting stripes parallel to the grooves eight times separates direct and global light (Nayar et al. 2006): the global image glows only inside the concave features, 1.28 times the direct light at the bottom of the inner corner and 5.1 times in the 60 deg groove, within 2.2 % / 6.3 % of the truth; the error falls monotonically from 128 to 8 px stripes and collapses to 54 % at 4 px, narrower than a pixel’s footprint. The fringe phase error is predictable before measuring: the argument of the direct-plus-global phasor from the transport matrix matches the error decoded by wrapped_phase from rendered fringes to 1e-15 rad and never exceeds asin(global/direct). One fringe over a 4 mm field misreads height by 282 / 579 / 183 um RMS; two-level temporal unwrapping (one fringe, then 16 px) picks the wrong order in 329 of 480 pixels (up to 7 orders) and barely helps; micro phase shifting (Gupta & Nayar 2012, periods 16 / 16.2 / 15.8 px, beat 1296 px) brings it to 0.76 / 1.97 / 0.02 um, leaving up to 19 um at the few pixels next to a V vertex. Continuous-wave ToF in a white room corner (4 m, 20 MHz) sees the corner rounded and up to 24 cm too far. Lambertian, extruded (2-D) shapes only; specular interreflection needs Mitsuba and is not modelled.
↑ Fringe Projection Misreads Even a White Groove - Separating the Interreflection, Predicting the Error Before Measuring, and Removing It with Micro Phase Shifting ―― A white Lambertian part (albedo 0.85, no gloss, no metal) with three 1 mm grooves - a 90 deg inner corner, a 60 deg V-groove and a pocket with rounded corners - measured by fringe projection; the truth is a light-transport matrix (2-D radiosity from Hottel’s crossed-strings form factors) and a direct-only rendering with zero bounces. Shifting stripes parallel to the grooves eight times separates direct and global light (Nayar et al. 2006): the global image glows only inside the concave features, 1.28 times the direct light at the bottom of the inner corner and 5.1 times in the 60 deg groove, within 2.2 % / 6.3 % of the truth; the error falls monotonically from 128 to 8 px stripes and collapses to 54 % at 4 px, narrower than a pixel’s footprint. The fringe phase error is predictable before measuring: the argument of the direct-plus-global phasor from the transport matrix matches the error decoded by wrapped_phase from rendered fringes to 1e-15 rad and never exceeds asin(global/direct). One fringe over a 4 mm field misreads height by 282 / 579 / 183 um RMS; two-level temporal unwrapping (one fringe, then 16 px) picks the wrong order in 329 of 480 pixels (up to 7 orders) and barely helps; micro phase shifting (Gupta & Nayar 2012, periods 16 / 16.2 / 15.8 px, beat 1296 px) brings it to 0.76 / 1.97 / 0.02 um, leaving up to 19 um at the few pixels next to a V vertex. Continuous-wave ToF in a white room corner (4 m, 20 MHz) sees the corner rounded and up to 24 cm too far. Lambertian, extruded (2-D) shapes only; specular interreflection needs Mitsuba and is not modelled.
↑ The measurement ―― 反射率 0.85 の Lambert 面。光沢も金属も無い。カメラは真上、プロジェクタは 20° 傾けて縞を溝に平行に投影。 (figure labels are in Japanese; the numbers are the same)
↑ 分離は「大域光が縞の周期より滑らか」という仮定で成り立つ。
↑ 白い Lambert の溝でも数百 µm。
↑ 周期を半分にするたびに誤差は 2〜3 分の 1。
↑ 白い壁(0.8)の 90° の隅を 20 MHz の連続波 ToF で。
↑ The animation ―― 縞をずらしながら撮った 8 枚。縞が当たっていない画素も、溝の中では向かいの面からの光で暗くならない。
py -3.11 examples/poc_interreflection_fringe_error.py
Source: examples/poc_interreflection_fringe_error.py
This run produced 11 figures in total - see them all
Ops used (notes): form_factors_2d · fringe_multipath_phase_error · groove_profile · high_frequency_patterns · micro_phase_decode · predict_fringe_phase_error · render_transport · separate_direct_global · tof_multipath_phase · tof_transport_depth · transport_matrix_radiosity · wrapped_phase
| A picture of an optical aberration can be scored as a picture: the coefficients can be read back out of it, and what comes back agrees with closed forms. The only operators used are ones already in the box - fit_zernike, which takes a disk image to a dictionary of coefficients, and wavefront_stats, which reports RMS, peak-to-valley and the Strehl ratio. Zernike polynomials form an orthogonal system on the unit disk and are the vocabulary of aberration itself: (2,0) is defocus, (2,+-2) astigmatism, (3,+-1) coma, (4,0) spherical. The twenty-eight modes with n at most six - the closed form (n+1)(n+2)/2 - all equal one exactly at the rim, and the number of radial zeros matches (n- | m | )/2 for every one of them. Orthogonality agrees with the closed form pi/(2(n+1))(1+delta) to 4.86e-06 on the diagonal, with 5.50e-07 off it. The first finding is that the operator’s own disclosure named the wrong cause. Its docstring used to say that at the default sampling up to ten percent leaks between modes, and advised raising the polar resolution. But the leak barely falls with resolution - the ratio per doubling never reaches two. The real cause is the zero background outside the pupil: the outermost ring draws the step into its bilinear interpolation. Extending the polynomial two percent past the rim, which moves the step away from the edge, takes the leak to 0.000041. This finding first corrected the disclosure (2026-10-07) and then the operator itself: since 2026-10-14 its polar grid is area-weighted, with Gauss-Legendre radial nodes that never sit on the rim, and the default worst leak across four modes fell from 0.098 to 0.021. The nodes crowd towards the rim, so discarding the outermost ring only halves what is left (0.0105); removing the step is what removes it. The second finding is that the picture turns while the measurement does not: rotation multiplies each coefficient by a phase, so the paired amplitude is exactly invariant - 1.1e-16 across six angles, and the sum of squares identical to the last bit - and even after drawing the wavefront and reading it back, three rotations spread the amplitude by 2.03e-09. The third is that the rings of the point spread function are set by a Bessel zero: the first dark ring sits at the first zero of J1 divided by pi, 1.219670 in units of lambda over D, and reading it off the picture lands within 3.6e-04 across a threefold range of pupil size. An unaberrated Strehl ratio is exactly one, and the Marechal approximation that wavefront_stats returns differs from the exact diffraction integral by 8.8e-06 at an RMS of 0.02 waves but by 2.0e-02 at 0.18 - a gap that opens by a factor of 2308. The fourth concerns interference fringes: the gradient of spherical aberration vanishes at a radius of one over root two, and the fringes open into a broad band exactly there; measured off the picture the radius comes out at 0.70462 against 0.70711, with the error falling in inverse proportion to the number of fringes. The fifth is that the rotational symmetry of the point spread reveals the azimuthal order, but even orders appear at twice their own frequency. Modes with m of zero are exactly rotationally symmetric; odd orders appear at their own harmonic; even orders vanish there exactly and appear only at twice it - which is why astigmatism looks fourfold rather than twofold. The reason is that an even order makes the wavefront periodic over half a turn, the pupil field centrosymmetric, and the first-order cross term identically zero, so halving the aberration divides the odd harmonics by two and the even ones by four, measured at 2.06 and 3.67. The sixth is that tone mapping is not decoration: rendering the outer rings linearly collapses them to a single level of an eight-bit image, while an inverse hyperbolic sine gives sixty-six - and because that curve is strictly monotone, not one of five thousand sampled pixel pairs changes order. Three predictions were wrong and have been left in, along with two defects in the author’s own measurement. No new operator was added. |
| *↑ Scoring an Aberration as a Picture: Zernike Polynomials (orthogonal aberration polynomials on a circular pupil) and the Point Spread Function ―― A picture of an optical aberration can be scored as a picture: the coefficients can be read back out of it, and what comes back agrees with closed forms. The only operators used are ones already in the box - fit_zernike, which takes a disk image to a dictionary of coefficients, and wavefront_stats, which reports RMS, peak-to-valley and the Strehl ratio. Zernike polynomials form an orthogonal system on the unit disk and are the vocabulary of aberration itself: (2,0) is defocus, (2,+-2) astigmatism, (3,+-1) coma, (4,0) spherical. The twenty-eight modes with n at most six - the closed form (n+1)(n+2)/2 - all equal one exactly at the rim, and the number of radial zeros matches (n- | m | )/2 for every one of them. Orthogonality agrees with the closed form pi/(2(n+1))(1+delta) to 4.86e-06 on the diagonal, with 5.50e-07 off it. The first finding is that the operator’s own disclosure named the wrong cause. Its docstring used to say that at the default sampling up to ten percent leaks between modes, and advised raising the polar resolution. But the leak barely falls with resolution - the ratio per doubling never reaches two. The real cause is the zero background outside the pupil: the outermost ring draws the step into its bilinear interpolation. Extending the polynomial two percent past the rim, which moves the step away from the edge, takes the leak to 0.000041. This finding first corrected the disclosure (2026-10-07) and then the operator itself: since 2026-10-14 its polar grid is area-weighted, with Gauss-Legendre radial nodes that never sit on the rim, and the default worst leak across four modes fell from 0.098 to 0.021. The nodes crowd towards the rim, so discarding the outermost ring only halves what is left (0.0105); removing the step is what removes it. The second finding is that the picture turns while the measurement does not: rotation multiplies each coefficient by a phase, so the paired amplitude is exactly invariant - 1.1e-16 across six angles, and the sum of squares identical to the last bit - and even after drawing the wavefront and reading it back, three rotations spread the amplitude by 2.03e-09. The third is that the rings of the point spread function are set by a Bessel zero: the first dark ring sits at the first zero of J1 divided by pi, 1.219670 in units of lambda over D, and reading it off the picture lands within 3.6e-04 across a threefold range of pupil size. An unaberrated Strehl ratio is exactly one, and the Marechal approximation that wavefront_stats returns differs from the exact diffraction integral by 8.8e-06 at an RMS of 0.02 waves but by 2.0e-02 at 0.18 - a gap that opens by a factor of 2308. The fourth concerns interference fringes: the gradient of spherical aberration vanishes at a radius of one over root two, and the fringes open into a broad band exactly there; measured off the picture the radius comes out at 0.70462 against 0.70711, with the error falling in inverse proportion to the number of fringes. The fifth is that the rotational symmetry of the point spread reveals the azimuthal order, but even orders appear at twice their own frequency. Modes with m of zero are exactly rotationally symmetric; odd orders appear at their own harmonic; even orders vanish there exactly and appear only at twice it - which is why astigmatism looks fourfold rather than twofold. The reason is that an even order makes the wavefront periodic over half a turn, the pupil field centrosymmetric, and the first-order cross term identically zero, so halving the aberration divides the odd harmonics by two and the even ones by four, measured at 2.06 and 3.67. The sixth is that tone mapping is not decoration: rendering the outer rings linearly collapses them to a single level of an eight-bit image, while an inverse hyperbolic sine gives sixty-six - and because that curve is strictly monotone, not one of five thousand sampled pixel pairs changes order. Three predictions were wrong and have been left in, along with two defects in the author’s own measurement. No new operator was added.* |
Terms: Zernike Polynomials — An orthogonal polynomial basis on the unit disk used to decompose pupil wavefront error into terms such as defocus and astigmatism.; PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.; Strehl — An image-quality ratio of the on-axis peak intensity of an aberrated point-spread function to that of the corresponding diffraction-limited system.
↑ The measurement ―― 波面を立体に起こす(箱の render3d で描画)。左上がデフォーカス(お椀)、右上が非点収差(鞍)、左下がコマ、右下が球面収差。収差の名前は、この形の名前です。立体にしても採点は変わりません —— 係数は絵ではなく多項式に属しているからで、後の図で絵を回しても数が動かないことを見せます。 (figure labels are in Japanese; the numbers are the same)
↑ 干渉縞 cos(2πW)。
↑ **濃淡の付け方は飾りではありません。
↑ 28 × 28 のグラム行列 ∫Z_n^m Z_n’^m’ ρdρdθ。
↑ wavefront_stats が返すストレール比はマレシャル近似 exp(−(2πσ)²) です。
↑ The animation ―― スルーフォーカス —— デフォーカスを −0.55 波から +0.55 波まで振って戻します。輪が同心のまま伸縮するのがデフォーカスの特徴で、ピントの前後で絵が対称になります(だから往復させても継ぎ目が見えません)。中央の 1 コマだけが無収差のエアリーで、そこだけストレール比が厳密に 1.000000000000 です。
↑ The animation ―― 絵は回るのに、測った数は動きません。 左が波面(コマ 0.62 + 非点収差 0.35 + 球面収差 0.18)、右がその点像。1 周ぶん回しています。回転は係数を exp(−imθ) 倍するだけなので、対の振幅 √(c₊²+c₋²) は厳密に不変 —— 6 通りの角度で最大 1.1e-16。しかも絵に描いてから fit_zernike で読み返しても、3 通りの回転で振幅の幅は 1.1e-09 しかありません(真値 0.664831)。★球面収差(m = 0)だけは絵そのものが回りません —— m = 0 は回転で変わらないモードだからで、これも絵から読めます。
py -3.11 examples/poc_zernike_aberrations.py
Source: examples/poc_zernike_aberrations.py
This run produced 14 figures in total - see them all
Ops used (notes): fit_zernike · wavefront_stats
Every speed and memory trick in the lineage that leads to large language models turns out to be a regrouping of the same arithmetic rather than an approximation, and each identity can be checked to machine precision. The operators are a new family of ten, written in numpy alone - no torch - and the input is a sequence of image patches (a 64 by 64 picture cut into sixty-four eight-by-eight tiles), so attention here is literally the entrance to a vision transformer and the claims hold on the picture side too. The first finding is that tiled attention with an online softmax, the core of FlashAttention, is exact: across seven tile sizes from one row to sixty-four the worst disagreement with the one-pass computation is 1.33e-15. What makes it fast is not a different computation but never forming the T by T matrix at all. A prediction was wrong here: error was expected to accumulate with the number of tiles, and it does, but sixty-four tiles give 1.33e-15 against 1.22e-15 for one - a factor of 1.1. The second is that linear attention is associativity and nothing else: (QK^T)V and Q(K^T V) agree to 1.18e-15, and the causal running-state form to 9.39e-16. The quadratic and linear costs are two bracketings of one product, and the operation counts stand in a ratio of exactly T over d (32 at T of 2048, an integer) – which is why the crossover sits at T equal to d. A second prediction was half right: the measured time ratio stalls at fifteen or sixteen instead of the thirty-two the formula promises, because the quadratic path becomes bandwidth-bound. Time, though, is decided by the machine: the same commit measured 11.6x here and 2.0x on the CI runner, so this PoC gates on the operation-count identity alone and reports the timing without a verdict.
↑ Every Speedup Is the Same Arithmetic Regrouped: Scoring Attention with Identities ―― Every speed and memory trick in the lineage that leads to large language models turns out to be a regrouping of the same arithmetic rather than an approximation, and each identity can be checked to machine precision. The operators are a new family of ten, written in numpy alone - no torch - and the input is a sequence of image patches (a 64 by 64 picture cut into sixty-four eight-by-eight tiles), so attention here is literally the entrance to a vision transformer and the claims hold on the picture side too. The first finding is that tiled attention with an online softmax, the core of FlashAttention, is exact: across seven tile sizes from one row to sixty-four the worst disagreement with the one-pass computation is 1.33e-15. What makes it fast is not a different computation but never forming the T by T matrix at all. A prediction was wrong here: error was expected to accumulate with the number of tiles, and it does, but sixty-four tiles give 1.33e-15 against 1.22e-15 for one - a factor of 1.1. The second is that linear attention is associativity and nothing else: (QK^T)V and Q(K^T V) agree to 1.18e-15, and the causal running-state form to 9.39e-16. The quadratic and linear costs are two bracketings of one product, and the operation counts stand in a ratio of exactly T over d (32 at T of 2048, an integer) – which is why the crossover sits at T equal to d. A second prediction was half right: the measured time ratio stalls at fifteen or sixteen instead of the thirty-two the formula promises, because the quadratic path becomes bandwidth-bound. Time, though, is decided by the machine: the same commit measured 11.6x here and 2.0x on the CI runner, so this PoC gates on the operation-count identity alone and reports the timing without a verdict.
↑ The measurement ―― 縦軸は 1e-16 単位。タイルを 64 枚に割っても相対差は 1.3e-15 —— 近似ではなく、同じ和を別の順で足しているだけ。★外した予言: 誤差はタイル数に比例して積もる → 64 倍のタイル数で 1.1 倍にしかならない。 (figure labels are in Japanese; the numbers are the same)
↑ 答えは同じ(相対差 1.2e-15)。
↑ 2 枚目と 3 枚目は差 4.4e-16 —— パッチを並べ替えてから戻すと元に戻る(置換同変)。
↑ 2 本の線は 64 本すべてで重なる(最大差 4.4e-16)。
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_attention_identities/09_convex_mixing.png)
↑ 行和が 1 なので出力は入力の凸結合で、値域 [0.025, 1.000] を出ない。
py -3.11 examples/poc_attention_identities.py
Source: examples/poc_attention_identities.py
This run produced 10 figures in total - see them all
Ops used (notes): attention_apply · attention_grouped · attention_linear · attention_scores · attention_softmax · attention_tiled · attention_weights · kv_cache_decode · project · rms_norm · rope_rotate
A phase reports a quantity only modulo its period: a part with four holes cannot tell its orientation apart every 90 degrees, and the phase of a 9 px band cannot tell displacements apart every 9 px. When the periods share no common divisor, the residues fix the value uniquely over their least common multiple (the Chinese remainder theorem) — the same arithmetic Ozaki scheme II uses to rebuild an exact FP64 product from INT8 products. The new family residue (5 ops) is graded in three steps. The theorem: integer CRT (Garner’s mixed radix) matches brute force over all of 0..692, and the real-valued version reproduces it bit for bit on integer input; with one redundant band it names the corrupted band (88.9 % of 3,000 cases) and otherwise answers ‘undecidable’ — never blaming the wrong band. Orientation: a part carrying 3-, 4- and 5-fold rings, each of which folds back every 120 / 90 / 72 degrees on its own (the sawtooth in the figure), is fixed over the full 360 degrees with a maximum error of 0.05 degrees, 0.84 degrees with 40 % of one ring smudged, and the ring with the worst residual pointed at the smudged one 27 of 30 times. Displacement: two regions of the public scikit-image textures moving separately by 20–30 px leave 1.8–2.9 % of the grass pixels far from the boundary off by more than 1 px, against about 50 % for the best pyramid Lucas-Kanade (3–6 levels), 100 % for an ideal single-band phase method, and a single value from global phase correlation. Where it breaks is in the same table: within 1.5 sigma of the boundary (36–85 %), photographs with large flat areas (camera, 23–36 %), and fields that change inside the window (50 % at 6 px amplitude) — a band narrow enough to keep the local frequency stable needs a window too wide to follow the field.
↑ Angles and Shifts No Single Phase Can Pin Down, Fixed by Bundling Phases of Different Periods — the Chinese Remainder Theorem in Images ―― A phase reports a quantity only modulo its period: a part with four holes cannot tell its orientation apart every 90 degrees, and the phase of a 9 px band cannot tell displacements apart every 9 px. When the periods share no common divisor, the residues fix the value uniquely over their least common multiple (the Chinese remainder theorem) — the same arithmetic Ozaki scheme II uses to rebuild an exact FP64 product from INT8 products. The new family residue (5 ops) is graded in three steps. The theorem: integer CRT (Garner’s mixed radix) matches brute force over all of 0..692, and the real-valued version reproduces it bit for bit on integer input; with one redundant band it names the corrupted band (88.9 % of 3,000 cases) and otherwise answers ‘undecidable’ — never blaming the wrong band. Orientation: a part carrying 3-, 4- and 5-fold rings, each of which folds back every 120 / 90 / 72 degrees on its own (the sawtooth in the figure), is fixed over the full 360 degrees with a maximum error of 0.05 degrees, 0.84 degrees with 40 % of one ring smudged, and the ring with the worst residual pointed at the smudged one 27 of 30 times. Displacement: two regions of the public scikit-image textures moving separately by 20–30 px leave 1.8–2.9 % of the grass pixels far from the boundary off by more than 1 px, against about 50 % for the best pyramid Lucas-Kanade (3–6 levels), 100 % for an ideal single-band phase method, and a single value from global phase correlation. Where it breaks is in the same table: within 1.5 sigma of the boundary (36–85 %), photographs with large flat areas (camera, 23–36 %), and fields that change inside the window (50 % at 6 px amplitude) — a band narrow enough to keep the local frequency stable needs a window too wide to follow the field.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 遠 = 境界から 60 px 以上。
↑ The animation ―― 針 = CRT で決めた向き(1 フレームごとに独立に推定)
py -3.11 examples/poc_residue_crt.py
Source: examples/poc_residue_crt.py
This run produced 5 figures in total - see them all
Ops used (notes): crt_displacement · harmonic_rotation · residue_crt · residue_fault_locate · residue_integer_crt
Alignment in lithography and nanoimprint overlays a mask and a wafer to nanometres. Two gratings of slightly different pitch (mask 2.00 µm, wafer 2.04 µm) are each erased by the camera blur (the direct image has a standard deviation of 9e-6), yet overlaid they make 102 µm fringes that magnify the shift fiftyfold. Eight new ops (moirealign, category moire_align of opsoptics): magnification and capture range, the Talbot effect of the proximity gap, the synthetic mark image, the fringe phase by lock-in (least squares at the known frequency) or by Takeda’s FFT method, phase to shift, the differential pair, reading a single, differential or four-quadrant 2-D mark, and the noise lower bound. The magnification depends on which grating moves: measured from the images 50.000 for the wafer and 51.000 for the mask (the often quoted M = p_w/ |
Δp | is the mask-moving case). Eleven 1 nm steps are read with an RMS error of 0.08 nm, and the Monte-Carlo spread matches the phase lower bound (Rife-Boorstyn times the Hann factor 1.5 times 2 for a noisy reference) with a ratio of 1.00. Against the baseline (a resolvable 20 µm bar mark read by cross-correlation and parabolic sub-pixel interpolation) the RMS error is 91 times smaller, but most of that is the parabola’s bias (it reads 17 % short); the random error alone differs only 2.5-fold (0.077 vs 0.19 nm), and the breakdown is printed. With a coarser camera (0.5 to 2 µm pixels) the correlation worsens 2.3-fold while the moiré does not change. Two pairs with opposite pitch differences cancel a 300 nm drift of the whole mark (one pair alone is off by 300/M = 6.2 nm) and a 30 % illumination tilt to within 0.03 nm, and a 2 % camera-scale error moves the reading by 0.007 nm because the conversion depends only on the pitches (the correlation is off by the full 2 %). The reading wraps every pitch (1.01 µm for the differential pair); a coarse correlation fixes the order and reads 0 to 5 µm with 0.14 nm RMS. Across a proximity gap the fringes return at the Talbot length (the lensless self-image distance 2p²/λ) of 14.5 µm and flip sign at half of it, and a flip between reference and measurement makes the reading jump by half a period (1020 nm). Honest limit: the 0.1 nm figures are a white-noise floor; published alignment accuracies of 10 to 30 nm are set by systematic errors such as mark asymmetry, gap and tilt. Nothing learned, nothing calibrated, 32 gates. |
*↑ Moiré (beat fringe between periodic patterns) Alignment — Two Invisible 2 µm Gratings Overlaid Turn a 1 nm Shift into a Readable Fringe Phase ―― Alignment in lithography and nanoimprint overlays a mask and a wafer to nanometres. Two gratings of slightly different pitch (mask 2.00 µm, wafer 2.04 µm) are each erased by the camera blur (the direct image has a standard deviation of 9e-6), yet overlaid they make 102 µm fringes that magnify the shift fiftyfold. Eight new ops (moirealign, category moire_align of opsoptics): magnification and capture range, the Talbot effect of the proximity gap, the synthetic mark image, the fringe phase by lock-in (least squares at the known frequency) or by Takeda’s FFT method, phase to shift, the differential pair, reading a single, differential or four-quadrant 2-D mark, and the noise lower bound. The magnification depends on which grating moves: measured from the images 50.000 for the wafer and 51.000 for the mask (the often quoted M = p_w/ |
Δp | is the mask-moving case). Eleven 1 nm steps are read with an RMS error of 0.08 nm, and the Monte-Carlo spread matches the phase lower bound (Rife-Boorstyn times the Hann factor 1.5 times 2 for a noisy reference) with a ratio of 1.00. Against the baseline (a resolvable 20 µm bar mark read by cross-correlation and parabolic sub-pixel interpolation) the RMS error is 91 times smaller, but most of that is the parabola’s bias (it reads 17 % short); the random error alone differs only 2.5-fold (0.077 vs 0.19 nm), and the breakdown is printed. With a coarser camera (0.5 to 2 µm pixels) the correlation worsens 2.3-fold while the moiré does not change. Two pairs with opposite pitch differences cancel a 300 nm drift of the whole mark (one pair alone is off by 300/M = 6.2 nm) and a 30 % illumination tilt to within 0.03 nm, and a 2 % camera-scale error moves the reading by 0.007 nm because the conversion depends only on the pitches (the correlation is off by the full 2 %). The reading wraps every pitch (1.01 µm for the differential pair); a coarse correlation fixes the order and reads 0 to 5 µm with 0.14 nm RMS. Across a proximity gap the fringes return at the Talbot length (the lensless self-image distance 2p²/λ) of 14.5 µm and flip sign at half of it, and a flip between reference and measurement makes the reading jump by half a period (1020 nm). Honest limit: the 0.1 nm figures are a white-noise floor; published alignment accuracies of 10 to 30 nm are set by systematic errors such as mark asymmetry, gap and tilt. Nothing learned, nothing calibrated, 32 gates.* |
Terms: Moiré — A long-period fringe produced at the difference spatial frequency when two patterns with slightly different pitch or orientation are superposed.
↑ The measurement ―― (a)〜(c) は同じカメラ(画素 0.5 µm、ぼけ σ 1.5 µm)と同じ雑音(σ 0.01)。2 µm の格子はカメラでは消えるが、2 枚の積の差の周波数(1/2.00 − 1/2.04 /µm)だけが残る。(d) は雑音なしの差動のマーク: 橙の線が A と B の境目、青の破線 = ずれ 0 の暗い縞、橙の実線と矢印 = ウェハを 600 nm 動かした後の同じ縞(A は -30.0 µm、B は +30.6 µm)。像全体が流れると両方が同じ向きに動くので、差を取ればずれだけが残る。 (figure labels are in Japanese; the numbers are the same)
↑ 下限 = Rife & Boorstyn の位相の下限 × Hann の係数 1.5 × 基準像の雑音で 2 倍、を p/(2π) で nm に。
↑ 逆向きのピッチ差の 2 対は、ずれでは逆に、マーク全体の流れでは同じ向きに縞が動く。
↑ 左上 A_x、右上 B_x、左下 A_y、右下 B_y。
↑ 印刷の版ずれと同じ不定性。
↑ The animation ―― 上: 20 µm の線を直接撮った像、下: モアレ。ずれを 1 コマ 51 nm ずつ、計 2.04 µm(ウェハのピッチ 1 つ)動かす。線は 0.1 画素ずつしか動かず見えないが、縞は 1 コマ 2.55 µm(5 画素)流れ、1 周して元に戻る(= 折り返し)。
py -3.11 examples/poc_moire_alignment.py
Source: examples/poc_moire_alignment.py
This run produced 8 figures in total - see them all
Ops used (notes): arrow · axes_frame · axes_transform · data_to_pixel · grid_lines · legend_box · moire_differential_shift · moire_magnification · moire_mark_read · moire_overlay_image · moire_phase · moire_phase_crlb · moire_phase_to_shift · moire_talbot_gap · nice_ticks · plot_series · text_box · ticks
Measured in one image, a stepped part looks larger where it is nearer, so an ordinary lens misreads its dimensions. On the published real images from a telecentric lens, the magnification matched the paper within 0.05 %. In the synthetic chapter, telecentric magnification barely moves with object height.
*↑ Telecentric Lenses for Dimensional Metrology — Move Only the Stop of the Same Lens and the Height-Dependent Magnification Disappears ―― The lead chapter measures the paper’s raw telecentric images (Stark et al., Sci. Rep. 15, 28690, 2025, CC BY 4.0) with Fullseye’s metrology ops and checks them against the paper: a family of ellipses sharing one centre and shape, fitted to the eight rings of an alignment plate (1 mm spacing) tilted by 29.5°, gives 252.13 ± 0.04 px/mm along the major axis, +0.05 % from the paper’s K1 = 252.0 px/mm (the paper’s own K1 and K2 spread by ±0.25 %, so the match is not read as finer than that). The telecentric signature: the centres of the concentric rings on the tilted plate do not drift with r² (β = 0.0035 ± 0.0034 px/mm², where a 25 mm entocentric lens would give 1.40 and move the outer centre by 101 px), so the entrance pupil lies beyond 10.5 m. Fitting a free ellipse to each ring instead fails on the outer rings whose arcs are shadowed down to 36–180°. A 3D-printed resin sphere (nominal radius 2.5 mm) is not a usable reference: its outline radius moves 8 % with the threshold. The synthetic chapters that follow sweep the failure conditions. Die bonding and connector-pin inspection measure features at different heights in one image. An ordinary (entocentric) lens magnifies nearer faces more, so the same dimension reads differently above and below a step; a telecentric lens puts the stop at a focal point so the chief rays run parallel to the axis. Seven new ops (telecentric, category telecentric of opsoptics): the same BK7-class singlet (f = 100 mm) with only the stop moved (entocentric, object-side and bi-telecentric), the real chief ray found by aiming at the stop itself so it works with the entrance pupil at infinity (where raytrace.chief_ray stops), magnification versus height, the error across a step, defocus blur, vignetting and a pinhole camera at the entrance pupil for render3d. Moving the object 5 mm closer on a fixed image plane changes the magnification by +6.9 % for a 25 mm machine-vision lens at 75 mm, +1.7 % for the 100 mm entocentric lens and −0.013 % for the telecentric ones; the real rays agree with L/(L − Δz) to 7e-5, and refocusing does not help (m = −f/(s − f) moves by +2.6 %). Rendered through the three lenses, a 5 mm pin top reads 6.9 % too large with the short lens (closed form to 7e-4) and its reading moves 8.6 % over a 6 mm stroke; 2,053 pixels of the bore wall show and only 0.948 of the hole bottom is visible (circle-overlap closed form 0.949), while the bi-telecentric view sees the whole bottom and no wall. The parts are rendered with real material appearance (a brushed aluminium fixture plate, a gold-plated pin, a black moulded package with tinned leads; machining marks and metal colours from complex refractive indices, a clear-coat resin, shadows and ambient occlusion), and under coaxial, dome and low-angle ring lighting a circle metrology model applied to the rendered image reads the pin top within 0.33 % of the closed form, +8.3 to 8.4 % across a 6 mm stroke for the short lens and within ±0.16 % for the bi-telecentric one; at a coarser 47 µm per pixel the low-angle ring reading drifts by 2 % because the dark-field edge is weak. What remains: the singlet’s pupil spherical aberration leaves 0.05° at 8 mm field (growing as h³) and 3 % distortion for the object-side design (0.005 % bi-telecentric); a stop shift moves the entrance pupil to −f²/δ as Newton’s equation says (0.04 % at 0.5 mm field, 11.8 % more at 8 mm); a residual 0.1° shifts a point 9.2 µm across a 5 mm step (closed form 8.7 µm). The front lens sets the field: the 50 % point at 11.6 mm falls 0.8 mm short of the paraxial overlap because pupil aberration shifts and widens the bundle. The blur slope matches |
m | ·NA/√2 within 0.5 %, and NA 0.033 (the smallest that resolves 22 µm) caps the depth of field at 0.68 mm. A second implementation, the open-source optiland, gives the same chief-ray image height to 9e-8 mm (with the same index from a public glass catalogue, so it checks the tracing and aiming code). Nothing learned, nothing calibrated, 29 gates.* |
↑ The measurement ―― 同じ BK7 相当の等凸レンズ(f = 100 mm)で、絞り(赤)の位置だけを変えた 3 種。物高 ±6 mm の主光線(実線)は、通常のレンズでは絞りへ向かって傾き、絞りを後側焦点に置くと物体側で光軸に平行になる。 (figure labels are in Japanese; the numbers are the same)
↑ 像面を固定して物体だけを動かした実の主光線の像の高さ。
↑ 負 = 主光線が光軸へ寄る(通常のレンズ側)、正 = 遠い物ほど大きく写るハイパーセントリック側。
↑ 両側テレセンで NA だけを変えた。
↑ 【実画像】リングごとに形(向き・縦横比・長半径)を族の値に固定して中心だけを解き直し、短軸方向のずれを取った。
↑ The animation ―― ワークを ±3 mm 上下させる。通常のレンズだけ像が伸び縮みし(水色の線 = 合焦面の輪郭)、穴の底が隠れる。
py -3.11 examples/poc_telecentric_metrology.py
Source: examples/poc_telecentric_metrology.py
This run produced 11 figures in total - see them all
Ops used (notes): add_metrology_object_circle_measure · annotate_scale_bar · apply_metrology_model · blast_normals · cast_shadow · chief_ray · chief_ray_angle · clearcoat_shade · create_metrology_model · defocus_spot_vs_depth · finish_shade · gen_measure_rectangle2 · glass_catalog · lens_equivalent_pinhole · lens_system · magnification_vs_depth · measure_pos · metal_mirror_rgb · micro_normals · paraxial_trace · telecentric_lens · telecentricity_error · text_box · tonemap_reinhard … (+2)
In inspection and metrology meetings the lens layout and the setup drawing (working distance, field of view, lighting angles) are the common language, and ordering a lens needs an ISO 10110 element drawing. Fullseye evaluated prescriptions with real rays and produced optical budgets, but drew nothing. Eight new ops (optdraw, category drawing of opsoptics) draw a meridional lens layout (surface sags, edges, stop, image plane, real rays aimed at the stop and coloured by field, EFL / BFL / track / image-height dimensions and a prescription table), a side and top view of a vision setup (field-of-view cone, working distance, depth-of-field band, five light families with elevation angles) and an ISO 10110 three-field element drawing whose tolerances come from the tolerance_analysis dictionary, and write them as SVG (true scale at 100 %) and DXF R12 with layers and line types using only the standard library. The gates score the files read back, not the picture: two textbook Cooke triplets match their published EFL to 7e-6 and 2e-5, drawn surfaces match the closed-form sphere sag to 6e-7 mm, ray kinks lie on the surfaces to 5e-7 mm, chief rays match raytrace.chief_ray to 5e-7 mm and dimension texts match at the printed precision. Telecentric lenses with the entrance pupil at infinity draw too, their chief-ray angles agreeing with telecentric.chief_ray_angle (a separate implementation) to 3e-7 degrees. An open-source DXF library (MIT) read all eleven files with zero audit errors. Inspecting the setup drawing at full size exposed a real bug: the drawn field of view (sensor × WD / f) and the budget magnification f/(WD − f) disagreed by 14 % in the same drawing, because optscene.vision_layout placed the pinhole at the focal length instead of the image distance f·WD/(WD − f); it is fixed, and a gate now counts the pixels of a 12 mm plate in a captured image (124 px against 124.2). Broken drawings turn every gate red. No training, no calibration, 53 gates.
↑ Optical Drawings Straight from the Calculation — Lens Layouts, Vision-Setup Drawings and ISO 10110 Element Drawings as SVG / DXF, Scored by Reading the Written Files Back ―― In inspection and metrology meetings the lens layout and the setup drawing (working distance, field of view, lighting angles) are the common language, and ordering a lens needs an ISO 10110 element drawing. Fullseye evaluated prescriptions with real rays and produced optical budgets, but drew nothing. Eight new ops (optdraw, category drawing of opsoptics) draw a meridional lens layout (surface sags, edges, stop, image plane, real rays aimed at the stop and coloured by field, EFL / BFL / track / image-height dimensions and a prescription table), a side and top view of a vision setup (field-of-view cone, working distance, depth-of-field band, five light families with elevation angles) and an ISO 10110 three-field element drawing whose tolerances come from the tolerance_analysis dictionary, and write them as SVG (true scale at 100 %) and DXF R12 with layers and line types using only the standard library. The gates score the files read back, not the picture: two textbook Cooke triplets match their published EFL to 7e-6 and 2e-5, drawn surfaces match the closed-form sphere sag to 6e-7 mm, ray kinks lie on the surfaces to 5e-7 mm, chief rays match raytrace.chief_ray to 5e-7 mm and dimension texts match at the printed precision. Telecentric lenses with the entrance pupil at infinity draw too, their chief-ray angles agreeing with telecentric.chief_ray_angle (a separate implementation) to 3e-7 degrees. An open-source DXF library (MIT) read all eleven files with zero audit errors. Inspecting the setup drawing at full size exposed a real bug: the drawn field of view (sensor × WD / f) and the budget magnification f/(WD − f) disagreed by 14 % in the same drawing, because optscene.vision_layout placed the pinhole at the focal length instead of the image distance f·WD/(WD − f); it is fixed, and a gate now counts the pixels of a 12 mm plate in a captured image (124 px against 124.2). Broken drawings turn every gate red. No training, no calibration, 53 gates.
↑ The measurement ―― 教科書の Cooke triplet(f = 50 mm、F/5、半画角 20°)の子午断面。面は処方の sag、光線は絞りを狙って追跡した実光線で、視野 0 / 14 / 20° を青・緑・赤に分けた。寸法線の BFL 42.42・EFL 50.00(H′ → F′)・全長 60.18・像高 18.14 は paraxial_trace の値で、SVG / DXF から読み戻した文字と表示の桁で一致する。縮尺 2:1。 (figure labels are in Japanese; the numbers are the same)
↑ もう 1 本の教科書の Cooke triplet(Geiser の例、f = 50 mm、F/4)。
↑ 絞りを後側焦点に置いた物体側テレセン。
↑ 同じカメラにドーム照明(半径 80 mm、天頂の穴は 20°)。
↑ バックライト(部品の下 30 mm、幅 80 mm)。
py -3.11 examples/poc_optics_drawings.py
Source: examples/poc_optics_drawings.py
This run produced 11 figures in total - see them all
Ops used (notes): chief_ray · chief_ray_angle · drawing_dxf · drawing_raster · drawing_svg · dxf_entities · glass · glass_map · layout_capture · lens_element_drawing · lens_section_drawing · lens_spec · lens_system · light_spec · paraxial_trace · scene_box · sensor_spec · svg_entities · telecentric_lens · tolerance_analysis · vision_layout · vision_setup_drawing
In a concave part such as a machined pocket, light reflected by one wall lights the floor (interreflection). The inspection renderer render_optscene counts direct light only, and render_studio, which solves interreflection, has no meaningful absolute brightness. Seven new ops (radiosity3d, category radiosity of opsoptics) add render_optscene_gi: the same scene and lights, the direct photometry kept to 1e-16, plus n bounces or the converged sum (Cohen et al. 1988 progressive radiosity, stopped on the residual unshot energy, our choice since the paper gives no number). In a pocket of albedo 0.7, 48 % of the floor’s brightness is interreflection. Calibrating albedo and light intensity with simcal: the direct-only model fits with albedo 1.32 (more light out than in), chi^2/nu 24 and four structured residual components, and is rejected; one bounce gives 0.87 and chi^2/nu 7.9; the converged model returns 0.6996 / 0.9999 (truth 0.70 / 1.00), chi^2/nu 1.01 and white residuals. Seven bounces bring the remainder under the 1.3 % noise (observed ratio 0.51, a GCI-style estimate trusted only in the asymptotic range). Howell’s catalogue values 0.199825 / 0.200044 are reproduced in closed form and numerically, and Table 2 of Bulczak, Lambers and Kolb 2018 (Sensors, CC BY 4.0: one bounce cuts the measured error to 0.20-0.47 of direct-only) is set beside this PoC’s 0.53.
↑ Counting Interreflection in a Machined Pocket to Convergence — The Direct-Light Model Fits with Albedo 1.32, the Converged Model Returns the Truth ―― In a concave part such as a machined pocket, light reflected by one wall lights the floor (interreflection). The inspection renderer render_optscene counts direct light only, and render_studio, which solves interreflection, has no meaningful absolute brightness. Seven new ops (radiosity3d, category radiosity of opsoptics) add render_optscene_gi: the same scene and lights, the direct photometry kept to 1e-16, plus n bounces or the converged sum (Cohen et al. 1988 progressive radiosity, stopped on the residual unshot energy, our choice since the paper gives no number). In a pocket of albedo 0.7, 48 % of the floor’s brightness is interreflection. Calibrating albedo and light intensity with simcal: the direct-only model fits with albedo 1.32 (more light out than in), chi^2/nu 24 and four structured residual components, and is rejected; one bounce gives 0.87 and chi^2/nu 7.9; the converged model returns 0.6996 / 0.9999 (truth 0.70 / 1.00), chi^2/nu 1.01 and white residuals. Seven bounces bring the remainder under the 1.3 % noise (observed ratio 0.51, a GCI-style estimate trusted only in the asymptotic range). Howell’s catalogue values 0.199825 / 0.200044 are reproduced in closed form and numerically, and Table 2 of Bulczak, Lambers and Kolb 2018 (Sensors, CC BY 4.0: one bounce cuts the measured error to 0.20-0.47 of direct-only) is set beside this PoC’s 0.53.
↑ The measurement ―― 同じシーン・同じドーム照明・同じカメラで、直接光だけの検査用描画(左上)と、反射を収束まで数えた描画(右上)。ポケットの底は相互反射で平均 48 % 明るく、角ほど強い(右下は像に占める相互反射の割合)。反射 0 回の render_optscene_gi は render_optscene と 1e-12 以内で一致する。 (figure labels are in Japanese; the numbers are the same)
↑ 直接光だけのモデルの残差の主成分(simcal_discrepancy)。
↑ 反射を k 回まで数えた像の誤差。
↑ 反射の回数だけでなく、パッチの細かさも収束させる。
↑ 3 つのモデルを同じ観測(ポケット + 反射率 0.5 の基準の板)に合わせた結果。
py -3.11 examples/poc_pocket_interreflection.py
Source: examples/poc_pocket_interreflection.py
This run produced 7 figures in total - see them all
Ops used (notes): camera_rays · form_factor_matrix · form_factor_parallel_rects · form_factor_perpendicular_rects · form_factor_polygons · intensity · light_source · optical_camera · radiosity_patches · radiosity_progressive_3d · render_optscene · render_optscene_gi · scene_box · scene_difference · scene_material · scene_plane · simcal_discrepancy · simcal_fit · simcal_model · simcal_observe · simcal_simulate · simcal_validate · trace_rays
Inspection optics rarely keep one straight axis: coaxial epi-illumination looks through a 45° beamsplitter, fold mirrors shrink the machine, and a tilted sensor brings a tilted part into focus. raytrace traced coaxial systems only and could not compute the ghost a beamsplitter’s back face makes. Thirteen new ops (raytrace3d, category noncoaxial) and optdraw.noncoaxial_drawing give every surface a 3-D pose (coordinate breaks decenter, then rotate x → y → z; undo reverses them) and trace with raytrace’s own surface equations: eight coaxial prescriptions match raytrace.trace_rays exactly (within 1e-12). A 45° fold reproduces the Householder mirror image of the unfolded image to 3e-14 mm, one reflection reverses the image and two restore it, and folding the camera arm of a coaxial inspection lens with two mirrors cuts the height from 393 to 249 mm with an unchanged image. Tilted-plate shift (Smith) matches real rays to 1e-12 mm and astigmatism (Coddington; the tangential image lies farther) to 1e-6 mm, and the Scheimpflug image tilt tan θ’ = m tan θ to 2e-4 degrees. The two-reflection ghost between the beamsplitter’s back and front faces carries 2.6 % of the image (Fresnel at 28° to 1e-6) and lays a shifted copy of a dent on a mirror-like part (m × 2t tan θ’ cos θ = 2.6 mm); a simple detector reports it as a false dent 0.010 mm from the predicted spot. A quarter-wave MgF2 layer only reaches 2.1 % at 45°; a two-layer coating tuned for the angle (0.38 %) drops the ghost to 0.19 % and the false dent disappears, and a 1° wedge throws the ghost 9.5 mm off the sensor. As a second implementation the open-source tracer optiland (MIT), built with the same absolute surface poses, agrees on every surface intersection of six systems to 1e-13 mm (CI checks recorded values to 1e-7 mm). Broken frames, fold orders and ghost paths turn the gates red. No training, no calibration, 19 gates.
↑ Non-Coaxial Optics — a Coaxial-Illumination Beamsplitter Paints a False Defect on a Mirror-Like Part, Removed by a Coating or a Wedge; Fold Mirrors Shrink the Machine ―― Inspection optics rarely keep one straight axis: coaxial epi-illumination looks through a 45° beamsplitter, fold mirrors shrink the machine, and a tilted sensor brings a tilted part into focus. raytrace traced coaxial systems only and could not compute the ghost a beamsplitter’s back face makes. Thirteen new ops (raytrace3d, category noncoaxial) and optdraw.noncoaxial_drawing give every surface a 3-D pose (coordinate breaks decenter, then rotate x → y → z; undo reverses them) and trace with raytrace’s own surface equations: eight coaxial prescriptions match raytrace.trace_rays exactly (within 1e-12). A 45° fold reproduces the Householder mirror image of the unfolded image to 3e-14 mm, one reflection reverses the image and two restore it, and folding the camera arm of a coaxial inspection lens with two mirrors cuts the height from 393 to 249 mm with an unchanged image. Tilted-plate shift (Smith) matches real rays to 1e-12 mm and astigmatism (Coddington; the tangential image lies farther) to 1e-6 mm, and the Scheimpflug image tilt tan θ’ = m tan θ to 2e-4 degrees. The two-reflection ghost between the beamsplitter’s back and front faces carries 2.6 % of the image (Fresnel at 28° to 1e-6) and lays a shifted copy of a dent on a mirror-like part (m × 2t tan θ’ cos θ = 2.6 mm); a simple detector reports it as a false dent 0.010 mm from the predicted spot. A quarter-wave MgF2 layer only reaches 2.1 % at 45°; a two-layer coating tuned for the angle (0.38 %) drops the ghost to 0.19 % and the false dent disappears, and a 1° wedge throws the ghost 9.5 mm off the sensor. As a second implementation the open-source tracer optiland (MIT), built with the same absolute surface poses, agrees on every surface intersection of six systems to 1e-13 mm (CI checks recorded values to 1e-7 mm). Broken frames, fold orders and ghost paths turn the gates red. No training, no calibration, 19 gates.
↑ The measurement ―― 同軸落射の検査レンズ(物体 → 45° の半透鏡 → 2 枚玉 → カメラ)の設置図。左はまっすぐ、右はカメラ側を 2 枚の 45° の鏡で折った版で、装置の高さが 393 → 249 mm に縮む。像の RMS は変わらない(門)。図は noncoaxial_drawing が大域の座標で描いた実寸の断面で、光線は絞り(L1)を狙った実光線。 (figure labels are in Japanese; the numbers are the same)
↑ 同軸落射の光学系: 横から来る照明(赤)を半透鏡が真下の部品へ曲げ、部品で鏡面反射した光が半透鏡を透過して2 枚玉に入る(青)。
↑ 集光する光束(f/25)に 5 mm の平板を傾けて入れたときの、主光線の横ずれと、接線像と球欠像の距離(非点隔差)。
↑ 2 枚玉(f ≈ 100 mm)で 200 mm 先の 30° に傾いた面を撮る。
↑ 全ての 2 面の組(N(N−1)/2 = 10)のうち、像面に光が届く組の、像に対するエネルギー比(届く割合を掛けた値)。
py -3.11 examples/poc_noncoaxial_optics.py
Source: examples/poc_noncoaxial_optics.py
This run produced 6 figures in total - see them all
Ops used (notes): beamsplitter_branches · drawing_raster · ghost_analysis · ghost_image · ghost_path · image_orientation · lens_system · noncoaxial_drawing · noncoaxial_example · noncoaxial_layout · noncoaxial_spot · noncoaxial_system · noncoaxial_trace · opd_samples · paraxial_trace · ray_bundle · scheimpflug_tilt · surface_reflectance · text_box · tilted_plate_shift · trace_rays
An imaging model with plate tilt, orientation, scale, radial distortion k1 and the inverse entrance-pupil distance 1/L (telecentricity) is calibrated with simcal on the ring centrelines of a real 29.5-degree tilted alignment plate (Stark et al., Sci. Rep. 2025, CC BY 4.0). With a noise level independent of the model (local-quadratic pure error, 1.12 px) the absolute chi-square gate fails (p = 7e-9) and the numbers are withdrawn: the 1.35 px residual carries smooth ring-by-ring discrepancy. Adding it as white variance passes all six gates, and only after checking that a ring jackknife agrees with the Laplace uncertainty are numbers reported: 252.14 ± 0.18 px/mm (paper K1 = 252.0) and 1/L = 0.50 ± 0.18 per metre, i.e. the entrance pupil is farther than 1.16 m (2 sigma), which rules out the ordinary f = 100 mm (302 mm) and f = 25 mm lenses. On synthetic data the eight coefficients are recovered at the bound, the ordinary lens’s 1/L = 3.31 per metre within 0.1 sigma of the closed form, an untilted plate makes the orientation ‘no influence’, and a low-order discrepancy makes Laplace 2.4 times optimistic, which the jackknife catches. The 2.7-sigma offset of 1/L from zero is unresolved. No training.
↑ Calibrating an Imaging Model on a Real Telecentric Image — the Scale Matches the Paper, the Entrance Pupil Is Only ‘Farther than 1 m’ ―― An imaging model with plate tilt, orientation, scale, radial distortion k1 and the inverse entrance-pupil distance 1/L (telecentricity) is calibrated with simcal on the ring centrelines of a real 29.5-degree tilted alignment plate (Stark et al., Sci. Rep. 2025, CC BY 4.0). With a noise level independent of the model (local-quadratic pure error, 1.12 px) the absolute chi-square gate fails (p = 7e-9) and the numbers are withdrawn: the 1.35 px residual carries smooth ring-by-ring discrepancy. Adding it as white variance passes all six gates, and only after checking that a ring jackknife agrees with the Laplace uncertainty are numbers reported: 252.14 ± 0.18 px/mm (paper K1 = 252.0) and 1/L = 0.50 ± 0.18 per metre, i.e. the entrance pupil is farther than 1.16 m (2 sigma), which rules out the ordinary f = 100 mm (302 mm) and f = 25 mm lenses. On synthetic data the eight coefficients are recovered at the bound, the ordinary lens’s 1/L = 3.31 per metre within 0.1 sigma of the closed form, an untilted plate makes the orientation ‘no influence’, and a low-order discrepancy makes Laplace 2.4 times optimistic, which the jackknife catches. The 2.7-sigma offset of 1/L from zero is unresolved. No training.
↑ The measurement ―― 【1〜3 章・実画像】入射瞳までの距離の逆数 1/L の標準誤差を 2 通りで出した。Laplace = 白い雑音を仮定したFisher 情報の逆、ジャックナイフ = リングを 1 本ずつ抜いて解き直した散らばり。食い違いの無い合成では2 つは同じ大きさ、リングごとに 0〜6 次の滑らかな食い違い(RMS 0.7 px、リングの半径と中心の癖を含む)を足すと Laplace が 2.4 倍小さく出る(楽観的)。実画像では食い違いを白い分散として足したあと、2 つは同じ大きさ(ジャックナイフ / Laplace = 0.8)—— この板の食い違いは係数の形を真似るほど低次ではない。 (figure labels are in Japanese; the numbers are the same)
↑ 【実画像】学習の測定線の残差を、リングごと・30° ごとに平均した(点の数は 4 以上)。
py -3.11 examples/poc_simcal_telecentric.py
Source: examples/poc_simcal_telecentric.py
This run produced 3 figures in total - see them all
Ops used (notes): chief_ray_angle · gaussian · project · simcal_discrepancy · simcal_fit · simcal_identifiability · simcal_model · simcal_observe · simcal_screen · simcal_validate · text_box
Subtracting the waviness before measuring roughness lets a deep scratch pull a linear Gaussian mean line down, so the healthy surface beside it turns into false ridges (2.5 times the true roughness on a profile with 4.3 % scratch samples). The three new roughness ops down-weight the scratch with Tukey’s biweight and iterate, and the mean line stays put (0.97 times). The transfer functions match their closed forms (0.5 at the cutoff; the existing surface_filter gave 0.5095 because of the standard’s literal truncation and was fixed by widening it). The breakdown lies between 35 % and 40 % contamination: at 40 % the mean line is off by 3.5 µm and no better than the linear one. Fixing the weight function to 1 fails the gate, and a non-converging iteration is refused. The truth is closed forms plus a synthetic waviness; no real stylus profile has been checked yet.
↑ Deep Scratches Dent the Mean Line — How Much Contamination Robust Gaussian and Spline Filters Survive ―― Subtracting the waviness before measuring roughness lets a deep scratch pull a linear Gaussian mean line down, so the healthy surface beside it turns into false ridges (2.5 times the true roughness on a profile with 4.3 % scratch samples). The three new roughness ops down-weight the scratch with Tukey’s biweight and iterate, and the mean line stays put (0.97 times). The transfer functions match their closed forms (0.5 at the cutoff; the existing surface_filter gave 0.5095 because of the standard’s literal truncation and was fixed by widening it). The breakdown lies between 35 % and 40 % contamination: at 40 % the mean line is off by 3.5 µm and no better than the linear one. Fixing the weight function to 1 fails the gate, and a non-converging iteration is refused. The truth is closed forms plus a synthetic waviness; no real stylus profile has been checked yet.
↑ The measurement ―― λ = λc = 80 µm でガウス・スプラインとも 0.5。点は閉形式の線の上に乗る(ガウス 1e-7、スプライン 1e-12 の差)。スプラインは遮断が鋭い。 (figure labels are in Japanese; the numbers are the same)
↑ 細い線が傷つき断面。
↑ 線形の引きずりは汚染に比例して増える。
py -3.11 examples/poc_robust_profile_filter.py
Source: examples/poc_robust_profile_filter.py
This run produced 4 figures in total - see them all
Ops used (notes): robust_gaussian_profile · robust_gaussian_surface · robust_spline_profile · surface_filter
Per-pixel quadrature demodulation (lock-in) is made a general op on an image sequence, and the three uses of a time-correlation image sensor are shown as variants of one function. Nine new ops (lockin, category lockin of opsoptics): lockin_demodulate (amplitude, phase, DC, in-phase, quadrature and the window leakage), lockin_amplitude, lockin_phase, lockin_noise_floor (closed-form amplitude and phase noise from the SNR), lockin_walsh_codes, lockin_separate (coded lights by least squares), lockin_ring_synthesize, lockin_ring_normals (normals from the first-harmonic response of a multipole-modulated ring) and lockin_thermal_wave (exact surface temperature of a periodically heated plate). A sinusoid of amplitude 2.5, phase 0.7 rad and DC 1.5 is returned to 6.6e-15, and detuning the reference rolls the amplitude off as a sinc (0.4977 at delta-f times T = 0.6033, 0 at 1.0). A length that is not a whole number of periods is refused for the rectangular window; declaring hann returns the leakage in the table (amplitude error 0.0156 at 3.75 periods against a declared bound of 0.0157). With white noise the phase standard deviation at component SNR 45, 11 and 1.7 is 0.0220, 0.0888 and 0.7244 rad measured against 0.0221, 0.0887 and 0.7306 from the closed form of the Rice phase distribution, within 0.9 % (the small-noise approximation is 19 % off at SNR 1.7). (a) Running the phase of an 8-source ring once around the azimuth makes the albedo drop out of the first-harmonic response and leaves the normals: on a surface with an albedo stripe pattern (tilt up to 28.8 degrees) the angle error is 1.0e-13 degrees, and the correlation with the true tilt is -0.035 for the mean image against 1.000000 for the ring normals. The quadrupole reads 0 on a Lambertian surface (3.5e-16). With noise sigma 0.004 the gradient error is 0.0069 measured against 0.0069 predicted (normal-angle RMS 0.56 degrees). A slope beyond the shadow limit is marked invalid instead of given a normal. (b) Three lights switched by Walsh codes are separated to 1.9e-15 even with daylight 2.6 to 4.2 times the lamp, while the best two-frame difference has an error of 0.94 (as large as the lamp image). Under flicker and slow drift the error of lamp 1 is 0.053, 0.031 after a drift column is added, and the flicker shows in the residual image. (c) A 3 mm CFRP plate heated at 0.2 Hz (thermal diffusion length 0.818 mm) has a phase that equals the exact -45 degrees + arg coth((1+j)L/mu) to 1.0e-15, and uneven heating changes the amplitude but not the phase (difference 5.6e-16). The phase standard deviation of the sound area under noise is 0.0213 rad measured against 0.0214 predicted. Voids at 0.3 / 0.6 / 1.0 / 1.5 mm are read from the phase as 0.30 / 0.60 / 1.00 / 1.54 mm (the phase peaks at L/mu = 1.963; deeper voids cannot be read uniquely). Detuning the reference by 10 %, a wrong zenith angle, an undeclared window and dependent codes each turn a gate red or are refused. No learning, no calibration, 25 gates.
↑ Per-Pixel Quadrature Demodulation (Lock-in) — Ring-Illumination Normals, Coded-Light Daylighting and Lock-in Thermography, Scored Against Closed Forms and the Rice Phase Distribution ―― Per-pixel quadrature demodulation (lock-in) is made a general op on an image sequence, and the three uses of a time-correlation image sensor are shown as variants of one function. Nine new ops (lockin, category lockin of opsoptics): lockin_demodulate (amplitude, phase, DC, in-phase, quadrature and the window leakage), lockin_amplitude, lockin_phase, lockin_noise_floor (closed-form amplitude and phase noise from the SNR), lockin_walsh_codes, lockin_separate (coded lights by least squares), lockin_ring_synthesize, lockin_ring_normals (normals from the first-harmonic response of a multipole-modulated ring) and lockin_thermal_wave (exact surface temperature of a periodically heated plate). A sinusoid of amplitude 2.5, phase 0.7 rad and DC 1.5 is returned to 6.6e-15, and detuning the reference rolls the amplitude off as a sinc (0.4977 at delta-f times T = 0.6033, 0 at 1.0). A length that is not a whole number of periods is refused for the rectangular window; declaring hann returns the leakage in the table (amplitude error 0.0156 at 3.75 periods against a declared bound of 0.0157). With white noise the phase standard deviation at component SNR 45, 11 and 1.7 is 0.0220, 0.0888 and 0.7244 rad measured against 0.0221, 0.0887 and 0.7306 from the closed form of the Rice phase distribution, within 0.9 % (the small-noise approximation is 19 % off at SNR 1.7). (a) Running the phase of an 8-source ring once around the azimuth makes the albedo drop out of the first-harmonic response and leaves the normals: on a surface with an albedo stripe pattern (tilt up to 28.8 degrees) the angle error is 1.0e-13 degrees, and the correlation with the true tilt is -0.035 for the mean image against 1.000000 for the ring normals. The quadrupole reads 0 on a Lambertian surface (3.5e-16). With noise sigma 0.004 the gradient error is 0.0069 measured against 0.0069 predicted (normal-angle RMS 0.56 degrees). A slope beyond the shadow limit is marked invalid instead of given a normal. (b) Three lights switched by Walsh codes are separated to 1.9e-15 even with daylight 2.6 to 4.2 times the lamp, while the best two-frame difference has an error of 0.94 (as large as the lamp image). Under flicker and slow drift the error of lamp 1 is 0.053, 0.031 after a drift column is added, and the flicker shows in the residual image. (c) A 3 mm CFRP plate heated at 0.2 Hz (thermal diffusion length 0.818 mm) has a phase that equals the exact -45 degrees + arg coth((1+j)L/mu) to 1.0e-15, and uneven heating changes the amplitude but not the phase (difference 5.6e-16). The phase standard deviation of the sound area under noise is 0.0213 rad measured against 0.0214 predicted. Voids at 0.3 / 0.6 / 1.0 / 1.5 mm are read from the phase as 0.30 / 0.60 / 1.00 / 1.54 mm (the phase peaks at L/mu = 1.963; deeper voids cannot be read uniquely). Detuning the reference by 10 %, a wrong zenith angle, an undeclared window and dependent codes each turn a gate red or are refused. No learning, no calibration, 25 gates.
Terms: lock-in — A method that correlates with in-phase and quadrature references at a known frequency to recover a periodic signal’s amplitude and phase from broadband noise.
↑ The measurement ―― Walsh 符号 3 本で断続する 3 灯。分離した灯 1 は真値と 1e-12 で一致する。 (figure labels are in Japanese; the numbers are the same)
↑ CFRP 板 3 mm、加熱 0.2 Hz(μ = 0.81 mm)。
↑ 周期加熱の熱波が、厚さ L の層の底(断熱面)で反射して表面に戻る遅れ。
↑ The animation ―― 周期加熱のコマを順に加えると、深さごとの位相像が収束する。
py -3.11 examples/poc_lockin_imaging.py
Source: examples/poc_lockin_imaging.py
This run produced 5 figures in total - see them all
Ops used (notes): lockin_amplitude · lockin_demodulate · lockin_noise_floor · lockin_phase · lockin_ring_normals · lockin_ring_synthesize · lockin_separate · lockin_thermal_wave · lockin_walsh_codes
Counting cells, reading a nucleus’s DNA content, measuring vessel branching, tracking a wound’s area: all of these tend to be reported as one number, and there are situations in which that number is right anyway. Cell counting where over- and under-segmentation balance to a +0.3-cell bias; ploidy classification that survives a forgotten background subtraction; a calibration that returns the most stable and most wrong healing constant.
The 27 exhibits carry ground truth that a label image alone cannot hold — which cells overlap which, area and DNA content varying independently, a tree that satisfies the branching law exactly. Each docstring warns that calling a label image ‘the truth’ on real data erases the very thing being tested.
The thing to watch for is a method that appears to improve while the quantity it measures quietly swaps: the area classifier gets better with more blur because ‘area’ is leaking DNA content. Unless the reason for every improvement is traced, this kind of lie gets carried home as a result.
Reported trabecular thickness changes with the definition of truth even on one image. Blur and noise were applied to known-width struts, and distribution overlap was measured at 60 µm pixels. Two biases cancel in the average while the thickness distribution loses agreement as resolution falls.
↑ Trabecular Thickness, Separation and Bone Volume Fraction — The Plate Model and the Direct Method Disagree on the Same Image ―― A 2-D trabecular network drawn in closed form as a set of segments (median width 120 µm), observed through partial-volume blur, CT noise and a cupping bias. There is more than one ground truth: the length-weighted mean width is 104.8 µm, the largest-inscribed-circle definition gives 121.6 µm and the plate model 118.4 µm — the choice of truth moves the answer by 9–16 % before any threshold does. The resolution cliff hits the distribution, not the mean (overlap with truth 0.83 → 0.09 at 60 µm pixels; the mean survives because quantisation at -29.5 % and Otsu thickening at +26.6 % cancel). Noise breaks from two sides, speckles from σ 0.10 and gaps from σ 0.15, and an area opening removes only the speckles.
↑ The measurement ―― Tb.Th の平均は 2 px/骨梁でも持つが、BV/TV と分布は壊れている。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ この回の図
↑ この回の図
py -3.11 examples/poc_bone_trabecular_thickness.py
Source: examples/poc_bone_trabecular_thickness.py
This run produced 14 figures in total - see them all
Ops used (notes): blob_distance · blob_features · blob_label · dc_retinex · dist_transform · gaussian · get_region_thickness · local_thickness · opening_circle · otsu · sk_area_opening · threshold
Overlapping cells can yield the right total without the right individuals. Overlap was increased in images with known counts, then segmentation was scored on the densest set of 78 cells. Split and merge errors remain where count bias nearly vanishes, so totals cannot select the best setting.
↑ Counting Overlapping Cells — Count, Over-Segmentation and Under-Segmentation as Three Numbers ―― Synthetic overlapping cells with the count, over-segmentation and under-segmentation tallied separately. In the densest condition the null misses 25 of 78 cells, every one of them an under-segmentation. Sweeping the seed suppression finds a balance point where the count bias is +0.3 cells while 13.3 segmentation errors remain; report the count alone and it passes as the best setting.
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_cell_counting/02_h_tradeoff.png) |
| *↑ The measurement ―― 誤り合計の谷と | 偏り | の谷は同じ場所に来ない。どちらを最適と呼ぶかで答えが変わる。 (figure labels are in Japanese; the numbers are the same)* |
↑ 偏りが 0 を横切る間隔 6 で分割誤りは 13.3 件残る。
↑ 真値の前景率 11.6 %。
py -3.11 examples/poc_cell_counting.py
Source: examples/poc_cell_counting.py
This run produced 4 figures in total - see them all
Ops used (notes): circularity · distance_transform · fill_holes · gauss_filter · sk_otsu · vol_distance_transform · vol_label · vol_local_maxima · vol_watershed · xsk2_h_maxima
Two channels of vesicle-like puncta are scattered over a synthetic cell body, with 0 / 25 / 50 / 100 % of the B puncta placed exactly on A puncta so the true colocalization is known. After the bleed-through matrix [[1, α], [β, 1]], cytoplasm, PSF and photon noise, two unrelated channels give Pearson r=0.203 and Otsu-Manders M1=0.132 at α=β=10 %. Estimating α=0.0996 (truth 0.10) from single-stain controls and unmixing linearly restores r to 0.007, but Manders stays at 0.699 even for 100 % (the Gaussian tails below the Otsu threshold are lost; closed-form prediction 0.750). Pearson crosses 0.5 at symmetric α=0.282 (predicted 2−√3=0.268); blur only breaks Manders, with a step at σ=2.5 px where Otsu’s foreground jumps from puncta to the whole cell. The Costes shuffle test calls pure bleed-through ‘significant’ at p=0.000.
↑ Colocalization lies under bleed-through — Pearson and Manders break in different places ―― Two channels of vesicle-like puncta are scattered over a synthetic cell body, with 0 / 25 / 50 / 100 % of the B puncta placed exactly on A puncta so the true colocalization is known. After the bleed-through matrix [[1, α], [β, 1]], cytoplasm, PSF and photon noise, two unrelated channels give Pearson r=0.203 and Otsu-Manders M1=0.132 at α=β=10 %. Estimating α=0.0996 (truth 0.10) from single-stain controls and unmixing linearly restores r to 0.007, but Manders stays at 0.699 even for 100 % (the Gaussian tails below the Otsu threshold are lost; closed-form prediction 0.750). Pearson crosses 0.5 at symmetric α=0.282 (predicted 2−√3=0.268); blur only breaks Manders, with a step at σ=2.5 px where Otsu’s foreground jumps from puncta to the whole cell. The Costes shuffle test calls pure bleed-through ‘significant’ at p=0.000.
Terms: PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.; Otsu — A histogram thresholding method that chooses the threshold maximizing the variance between the two resulting classes.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ この回の図
↑ この回の図
py -3.11 examples/poc_colocalization_crosstalk.py
Source: examples/poc_colocalization_crosstalk.py
This run produced 11 figures in total - see them all
Ops used (notes): gauss_image · mat_lstsq · mat_solve · noise_sigma · otsu · photon_sample · reg_erode · stat_correlation
A brain-slice phantom of elliptical shells (skull / CSF / wrinkled cortex / WM, areas known from geometry) is multiplied by a surface-coil bias field and Rician noise, and the three tissue areas are measured with global three-class Otsu (xsk2_multiotsu). At 30 % amplitude GM is +20.2 % and WM -7.7 % while GM+WM is +0.0 % — the sum hides the error — and removing the noise flips the sign (GM -11.8 %). The geometric cliff prediction was 30 %; the measured cliff is 17.5 %. Naively smoothing log I damages a field-free image by GM +81.8 %; iterating on the segmentation residual (Wells-type) holds +1.8 % even at 40 %. Every corrector fails once the field is as fine as 4 px, and at SNR 15 GM is +8.5 % even without a field (WM is 2.6× larger, so the error lands on the smaller tissue).
↑ MRI Bias Field and Tissue Area — Grey and White Matter Fail in Opposite Directions, and the Sum Hides It ―― A brain-slice phantom of elliptical shells (skull / CSF / wrinkled cortex / WM, areas known from geometry) is multiplied by a surface-coil bias field and Rician noise, and the three tissue areas are measured with global three-class Otsu (xsk2_multiotsu). At 30 % amplitude GM is +20.2 % and WM -7.7 % while GM+WM is +0.0 % — the sum hides the error — and removing the noise flips the sign (GM -11.8 %). The geometric cliff prediction was 30 %; the measured cliff is 17.5 %. Naively smoothing log I damages a field-free image by GM +81.8 %; iterating on the segmentation residual (Wells-type) holds +1.8 % even at 40 %. Every corrector fails once the field is as fine as 4 px, and at SNR 15 GM is +8.5 % even without a field (WM is 2.6× larger, so the error lands on the smaller tissue).
Terms: Otsu — A histogram thresholding method that chooses the threshold maximizing the variance between the two resulting classes.
↑ The measurement ―― 雑音だけでは壊れず、場だけで GM と WM が逆向きに動く。 (figure labels are in Japanese; the numbers are the same)
↑ 幾何予測(しきい値固定・雑音なし)と大津の実測は同じ振幅で崖を越える。
↑ この回の図
↑ この回の図
↑ σ_b 4 px は皮質リボンの太さ。
py -3.11 examples/poc_mri_bias_field.py
Source: examples/poc_mri_bias_field.py
This run produced 11 figures in total - see them all
Ops used (notes): dc_homomorphic · eval_bspline_surface · eval_poly_surface · fit_bspline_surface · fit_poly_surface · overlay_labels · xsk2_multiotsu
Fluorescent nuclei whose DNA content D and area A were drawn with independent spread, classified by area and by integrated intensity. Even the true area misclassifies 15.4 %; integrated intensity misclassifies 0 %. Forgetting to subtract background leaves the classification intact while the DNA index alone breaks from 2.115 to 1.702 (-20 %) — invisible if you only watch the classification.
↑ Ploidy From Integrated Nuclear Intensity — Area Cannot Separate It ―― Fluorescent nuclei whose DNA content D and area A were drawn with independent spread, classified by area and by integrated intensity. Even the true area misclassifies 15.4 %; integrated intensity misclassifies 0 %. Forgetting to subtract background leaves the classification intact while the DNA index alone breaks from 2.115 to 1.702 (-20 %) — invisible if you only watch the classification.
↑ The measurement ―― 累積分布。積分輝度の 4n は 2.1 付近に固まり、面積の 2 本は大きく重なる。 (figure labels are in Japanese; the numbers are the same)
↑ 誤分類率はほぼ 0 のままだが、DNA 指数は背景とともに 2.00 から落ちる。
↑ 特徴量が分かれてさえいれば割り方は選ばない。
py -3.11 examples/poc_nuclei_ploidy.py
Source: examples/poc_nuclei_ploidy.py
This run produced 4 figures in total - see them all
Ops used (notes): aperture_photometry · blob_features · blob_label · blob_overlay · gaussian · median · sg_gmm_segment · sk_otsu
A synthetic vessel tree obeying Murray’s law exactly, skeletonised and measured for branch points, radii and the exponent. Counting branch pixels directly gives 47 pixels for 25 branches; grouping them into connected components gives exactly 25. Spurs come not from the skeletonisation but from boundary roughness (spurious branches 0 → 72), and radii within 3 px of a branch are overestimated by +26.2 %.
↑ Extracting a Vessel Network — Spurs, Overestimated Radii Near Branches, and a Fragile Exponent ―― A synthetic vessel tree obeying Murray’s law exactly, skeletonised and measured for branch points, radii and the exponent. Counting branch pixels directly gives 47 pixels for 25 branches; grouping them into connected components gives exactly 25. Spurs come not from the skeletonisation but from boundary roughness (spurious branches 0 → 72), and radii within 3 px of a branch are overestimated by +26.2 %.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 分岐から 2 px の点は 1 つも解けなかったので図から外した
↑ この回の図
py -3.11 examples/poc_vessel_network.py
Source: examples/poc_vessel_network.py
This run produced 5 figures in total - see them all
Ops used (notes): blob_features · blob_label · distance_transform · gauss_filter · medial_axis_points · r2_split_skeleton_lines · sk_medial · skeleton · skeleton_branches3d · skeleton_endpoints3d · skeleton_junctions3d · skeleton_prune3d · skeletonize_vol · thinning · vol_distance_transform
A star-shaped wound on a millimetre plane (closed-form area), photographed day by day through a pinhole camera to estimate the healing constant k. A 4 % change in distance moves the area by 7.7 %; with the distance drifting 1.2 % per day the null reports k = 0.1424 against a true 0.1200 (+18.7 %). Its standard deviation, 0.0049, is smaller than the 0.0059 of recalibrating every time — the most stable and the most wrong answer.
↑ Wound Area Over Time — Calibration Error Enters the Area Squared ―― A star-shaped wound on a millimetre plane (closed-form area), photographed day by day through a pinhole camera to estimate the healing constant k. A 4 % change in distance moves the area by 7.7 %; with the distance drifting 1.2 % per day the null reports k = 0.1424 against a true 0.1200 (+18.7 %). Its standard deviation, 0.0049, is smaller than the 0.0059 of recalibrating every time — the most stable and the most wrong answer.
↑ The measurement ―― ゼロ点の面積誤差。実測は閉形式の 2 乗則に乗り、線形近似からは外れる。 (figure labels are in Japanese; the numbers are the same)
↑ ゼロ点は毎回もっともらしい値を返しながら、傾きだけが系統的に急になる。
↑ 較正は d に対して (1+d)²-1、しきい値はほぼ線形。
↑ ゼロ点は散らばりがいちばん小さく、偏りがいちばん大きい。
↑ The animation ―― 動画(800 × 544、12 fps、143 コマ): 真の面積 A0·exp(-kt)(k = 0.12 /day)で縮む創面を 8 日撮る。撮影距離は 1 日 +1.2 % 漂い(この 1 本では 448 → 495 mm)、傾き・方位・回転も毎回変わる(日と日の間は条件を補間した仮想の撮影、整数日のコマが門と同じ 1 枚)。左 = カメラの像(水色 = 測った塊、紫の十字 = 較正標識)、右 = 正対化した像。M0(1 枚目だけで較正)は0 日目 +10.7 % から 7 日目 -9.9 % へ真値の下へ漂い、下の対数グラフで M0 の傾きだけが急になる。この 1 本の k は M0 0.1473 / M1 0.1253 / M2 0.1174(真値 0.1200)、8 seed の平均は M0 0.1424(+18.7 %)/ M1 0.1225(+2.1 %)/ M2 0.1200(+0.0 %)。
py -3.11 examples/poc_wound_area_tracking.py
Source: examples/poc_wound_area_tracking.py
This run produced 6 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · blob_features · blob_label · blob_select_largest · contour_to_world_plane_xld · data_to_pixel · gaussian · hom_vector_to_proj_hom_mat2d · image_to_world_plane · plot_series · text_box · ticks · warp_by_plane
The complete larval Drosophila connectome (Winding 2023: 2,952 neurons, 110,677 edges) used as a fixed recurrent network with only a closed-form ridge readout reads an MNIST subset (4,000 train, 1,000 test) at 91.9 % (ridge on raw pixels: 77.6 %). Prior work stops there; this exhibit adds the controls: a graph with every neuron’s in- and out-degree preserved but the edges rewired reaches 91.6 %, a random graph of the same density 91.9 %, a Gaussian random reservoir 91.5 %. The gap is +0.3 points over 3 seeds. What the readout uses is the reservoir as a mechanism, not the wiring evolution chose. The setting (input scale and regularisation) is chosen once on the connectome’s validation split and reused for every control.
↑ A Larval Connectome as a Reservoir Reads Digits — and the Wiring Is Not What Does It ―― The complete larval Drosophila connectome (Winding 2023: 2,952 neurons, 110,677 edges) used as a fixed recurrent network with only a closed-form ridge readout reads an MNIST subset (4,000 train, 1,000 test) at 91.9 % (ridge on raw pixels: 77.6 %). Prior work stops there; this exhibit adds the controls: a graph with every neuron’s in- and out-degree preserved but the edges rewired reaches 91.6 %, a random graph of the same density 91.9 %, a Gaussian random reservoir 91.5 %. The gap is +0.3 points over 3 seeds. What the readout uses is the reservoir as a mechanism, not the wiring evolution chose. The setting (input scale and regularisation) is chosen once on the connectome’s validation split and reused for every control.
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_larval_connectome_reservoir/02_activity_raster_connectome_vs_shuffle.png) |
| *↑ The measurement ―― | state | of the 300 highest-degree neurons over 6 steps for one test digit: connectome | shuffle (figure labels are in Japanese; the numbers are the same)* |
↑ test accuracy per reservoir variant (rows: no reservoir, connectome, shuffle, ER, Gaussian)
py -3.11 examples/poc_larval_connectome_reservoir.py
Source: examples/poc_larval_connectome_reservoir.py
This run produced 3 figures in total - see them all
Ops used (notes): graph_degree_preserving_shuffle · graph_degree_table · graph_spectral_radius · reservoir_encode · reservoir_from_graph · ridge_predict · ridge_readout
| The previous exhibit ended with ‘readout accuracy cannot tell the connectome from a random graph’. This one uses the same reservoir to look: the 3,000 somatic neurons of the MaleCNS with the most synapses (344,719 edges) receive a pulse into the right optic lobe only, and the activity is drawn on the rotating 3-D brain and VNC (grey = all 141,781 somata, orange = right, blue = left). Next to it, a graph with every neuron’s in- and out-degree preserved but the edges rewired gets the same pulse through the same input matrix. Measured: in the connectome the | x | -weighted mean distance from the stimulus grows 88 → 230 µm over 17 steps, and neurons light in order optic (latency 0) → central (2) → descending (2), never reaching the VNC within 36 steps. In the shuffle the activity scatters to 300 µm in 3 steps and 93 % of the farthest quarter of the neurons light within the first period (connectome: 0 %). The spatial structure of the wiring that accuracy could not see is visible in motion. One brightness scale for every frame. Besides the rotating view, a second animation shows the same instant from three fixed directions at once (dorsal, lateral, along the body axis; views=). Raw data is never committed (the subgraph is built from local feather files and cached; without them a distance-wired synthetic surrogate runs the same path). |
| ↑ Watching a Pulse Travel the Wiring on the 3-D Fly Brain — Connectome vs Degree-Preserving Shuffle ―― The previous exhibit ended with ‘readout accuracy cannot tell the connectome from a random graph’. This one uses the same reservoir to *look: the 3,000 somatic neurons of the MaleCNS with the most synapses (344,719 edges) receive a pulse into the right optic lobe only, and the activity is drawn on the rotating 3-D brain and VNC (grey = all 141,781 somata, orange = right, blue = left). Next to it, a graph with every neuron’s in- and out-degree preserved but the edges rewired gets the same pulse through the same input matrix. Measured: in the connectome the | x | -weighted mean distance from the stimulus grows 88 → 230 µm over 17 steps, and neurons light in order optic (latency 0) → central (2) → descending (2), never reaching the VNC within 36 steps. In the shuffle the activity scatters to 300 µm in 3 steps and 93 % of the farthest quarter of the neurons light within the first period (connectome: 0 %). The spatial structure of the wiring that accuracy could not see is visible in motion. One brightness scale for every frame. Besides the rotating view, a second animation shows the same instant from three fixed directions at once (dorsal, lateral, along the body axis; views=). Raw data is never committed (the subgraph is built from local feather files and cached; without them a distance-wired synthetic surrogate runs the same path).* |
↑ The measurement ―― a pulse into the right optic lobe (orange = right, blue = left somata; grey = all 141781 somata) propagates through the real wiring (left) and through the same degrees rewired at random (right); 3000 neurons, 344719 edges, 36 steps, one brightness scale for every frame (figure labels are in Japanese; the numbers are the same)
| *↑ when each neuron first lights up (yellow = step 0, orange = step 3, blue = step 6 or later, grey = never within 36 steps): connectome | shuffle* |
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_malecns_activity_wave/04_activity_spread_mean_distance.png) |
| *↑ graph_activity_spread: | x | -weighted mean distance from the stimulated somata* |
py -3.11 examples/poc_malecns_activity_wave.py
Source: examples/poc_malecns_activity_wave.py
This run produced 4 figures in total - see them all
Ops used (notes): graph_activation_latency · graph_activity_spread · graph_degree_preserving_shuffle · points_activity_video · reservoir_from_graph · reservoir_states · text_box
The MaleCNS annotations give every optic-lobe neuron a hexagonal column (the column that belongs to one ommatidium of the retina). The 892 right-eye columns (top 3 neurons each) plus 1,400 central and descending hubs form a subgraph of 4,076 neurons and 150,487 edges, stimulated at the resolution of a single ommatidium. Animation 1: as the stimulated column moves along a row of the eye, the response (yellow = stimulated, orange = right, blue = left; scale = response peak excluding the stimulus) moves the same way inside the optic lobe — the correlation between the stimulated column and the response centroid is −0.92 for the connectome and +0.01 for the degree-preserving shuffle with the same input matrix. Retinotopy lives in the wiring and not in the degrees. Animation 2: a bar crossing the visual field is resampled onto the ommatidial lattice (fly_hex_resample) and each ommatidium’s brightness drives its column; the response follows the bar (dorsal and lateral views). In Studio, Tools ▸ Compound eye → brain runs the same parts interactively: hover to stimulate the column under the mouse, drag to orbit, feed the current Studio image through the eye, switch to the shuffle. The column-to-ommatidium mapping is an approximation by normalised hexagonal coordinates. Neither the raw data nor the subgraph is committed (cached from local feather files; without them a synthetic surrogate with hexagonal columns runs the same path).
↑ What the Compound Eye Sees, and Where the Brain Answers — Trace an Ommatidium and the Response Travels the Wiring ―― The MaleCNS annotations give every optic-lobe neuron a hexagonal column (the column that belongs to one ommatidium of the retina). The 892 right-eye columns (top 3 neurons each) plus 1,400 central and descending hubs form a subgraph of 4,076 neurons and 150,487 edges, stimulated at the resolution of a single ommatidium. Animation 1: as the stimulated column moves along a row of the eye, the response (yellow = stimulated, orange = right, blue = left; scale = response peak excluding the stimulus) moves the same way inside the optic lobe — the correlation between the stimulated column and the response centroid is −0.92 for the connectome and +0.01 for the degree-preserving shuffle with the same input matrix. Retinotopy lives in the wiring and not in the degrees. Animation 2: a bar crossing the visual field is resampled onto the ommatidial lattice (fly_hex_resample) and each ommatidium’s brightness drives its column; the response follows the bar (dorsal and lateral views). In Studio, Tools ▸ Compound eye → brain runs the same parts interactively: hover to stimulate the column under the mouse, drag to orbit, feed the current Studio image through the eye, switch to the shuffle. The column-to-ommatidium mapping is an approximation by normalised hexagonal coordinates. Neither the raw data nor the subgraph is committed (cached from local feather files; without them a synthetic surrogate with hexagonal columns runs the same path).
↑ The measurement ―― a stimulus of one eye column (+ its 6 neighbours) moves along a row of the right eye; yellow = stimulated neurons, orange/blue = response (scale = response peak); the connectome answers retinotopically, the shuffle does not (figure labels are in Japanese; the numbers are the same)
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_eye_to_brain/03_retinotopy_scatter.png) |
| *↑ | x | -weighted centroid of the responding optic-lobe neurons vs the stimulated column* |
↑ fly_hex_quantize(mode=log): a low-contrast soft bar (0.5 + 0.3, sigma 6 px) crossing the visual field is quantized to n bits per ommatidium before ent…
py -3.11 examples/poc_eye_to_brain.py
Source: examples/poc_eye_to_brain.py
This run produced 4 figures in total - see them all
Ops used (notes): fly_hex_quantize · points_activity_video · text_box
Automatic segmentation of serial EM sections leaves merges (two cells under one id) and splits (one cell under two ids); every prior detector is a deep network. This PoC counts the same two errors without learning, from one premise read two ways — a cell membrane (dark ridge) belongs only on label boundaries: a membrane chord crossing the inside of a label = merge suspect (closed rings, i.e. mitochondria, are excluded by their hole), a boundary without membrane = split suspect. Artificial merges and splits are injected into the ground-truth labels of CREMI sample A (12 slices of 512², raw data never committed); with thresholds chosen on the first 6 slices and every number reported on the last 6, splits reach AUC 1.00 (TPR 1.00 / FPR 0.11) and merges AUC 0.83 against area-matched negatives (TPR 0.12 / FPR 0.01; random 0.53, area only 0.70). Merge detection is weak: membrane-rich cells score like chords. Because an injected merge is the union of two large labels, measuring without area matching yields 0.87 from ‘big label = suspicious’ alone — the reason holdout_threshold, which separates the slices that choose the threshold from the slices that are measured, is an operator. The animation rotates the stacked cube with suspects on top (magenta = merge, cyan = split, brightness = score). New family emproof (7 ops, numpy + scipy only).
↑ A Second Opinion for EM Connectome Proofreading — Membranes Belong Only on Label Boundaries ―― Automatic segmentation of serial EM sections leaves merges (two cells under one id) and splits (one cell under two ids); every prior detector is a deep network. This PoC counts the same two errors without learning, from one premise read two ways — a cell membrane (dark ridge) belongs only on label boundaries: a membrane chord crossing the inside of a label = merge suspect (closed rings, i.e. mitochondria, are excluded by their hole), a boundary without membrane = split suspect. Artificial merges and splits are injected into the ground-truth labels of CREMI sample A (12 slices of 512², raw data never committed); with thresholds chosen on the first 6 slices and every number reported on the last 6, splits reach AUC 1.00 (TPR 1.00 / FPR 0.11) and merges AUC 0.83 against area-matched negatives (TPR 0.12 / FPR 0.01; random 0.53, area only 0.70). Merge detection is weak: membrane-rich cells score like chords. Because an injected merge is the union of two large labels, measuring without area matching yields 0.87 from ‘big label = suspicious’ alone — the reason holdout_threshold, which separates the slices that choose the threshold from the slices that are measured, is an operator. The animation rotates the stacked cube with suspects on top (magenta = merge, cyan = split, brightness = score). New family emproof (7 ops, numpy + scipy only).
↑ The measurement ―― suspects on the stacked cube: magenta = merge suspects (membrane chords), cyan = split suspects (membrane-free boundaries), brightness = score, grey = all label boundaries (figure labels are in Japanese; the numbers are the same)
↑ ROC on the held-out slices; thresholds were chosen on the other half
↑ tau = smallest threshold with train FPR <= 5 %; every number in the test columns comes from slices the threshold never saw
py -3.11 examples/poc_em_second_opinion.py
Source: examples/poc_em_second_opinion.py
This run produced 4 figures in total - see them all
Ops used (notes): holdout_threshold · points_activity_video · seg_boundary_membrane_gap · seg_inject_merge · seg_inject_split · seg_label_changes · seg_membrane_chord_score · seg_membrane_response
You do not think about individual muscles when you raise an arm: the tens of thousands of brain states must be folded into a few commands somewhere, and in the fly that somewhere is visible in the wiring — MaleCNS v1.0 (Janelia, CC BY 4.0): 32,164 central-brain interneurons → the narrow neck of 1,314 descending neurons (DN) → 13,161 nerve-cord interneurons → 708 motor neurons (MN). With four ops added to conngraph (graph_layer_propagate / graph_block_shuffle / states_participation_ratio / states_layer_dimension) the brain → DN → VNC → MN subgraph (4,022 neurons) is driven by 400 random sparse stimuli and the effective dimension of each layer’s states (participation ratio, Gao et al. 2017) is read. With sparse firing (kWTA 10 %) it falls monotonically 282 > 61 > 8.7 > 2.4; picking 708 columns of the brain states still gives 249, so the fall is not the layer size. A control that keeps every receiver’s input weights but shuffles who sends them leaves the MN at 21.9 — the VNC → muscle stage compresses by the specific wiring, while the neck (DN) stage matches the control (61 vs 62) and compresses by convergence alone. Honest breakdown: the raw PR is also pulled by a heavy tail (a few stimuli drive the MN 17× harder than the median), so the direction-only dimension (every response scaled to unit norm) was measured too — real wiring 25 vs control 54 (kWTA), 7 vs 42 (linear). The same formula on Physical AI: joint trajectories of a humanoid robot’s RL walking and running score 1.5–4.1 (median 3.2), dance 9.3, fight 11.9, and evis muscle activations (up to 73 muscles) 5.8–9.6. An order-of-magnitude comparison, not a claim of identity. Raw data and subgraphs are never committed.
↑ Motor Quantisation — Where the Command Dimension Collapses Between Brain and Muscle (the Fly’s Neck and an RL Policy’s Joints on One Scale) ―― You do not think about individual muscles when you raise an arm: the tens of thousands of brain states must be folded into a few commands somewhere, and in the fly that somewhere is visible in the wiring — MaleCNS v1.0 (Janelia, CC BY 4.0): 32,164 central-brain interneurons → the narrow neck of 1,314 descending neurons (DN) → 13,161 nerve-cord interneurons → 708 motor neurons (MN). With four ops added to conngraph (graph_layer_propagate / graph_block_shuffle / states_participation_ratio / states_layer_dimension) the brain → DN → VNC → MN subgraph (4,022 neurons) is driven by 400 random sparse stimuli and the effective dimension of each layer’s states (participation ratio, Gao et al. 2017) is read. With sparse firing (kWTA 10 %) it falls monotonically 282 > 61 > 8.7 > 2.4; picking 708 columns of the brain states still gives 249, so the fall is not the layer size. A control that keeps every receiver’s input weights but shuffles who sends them leaves the MN at 21.9 — the VNC → muscle stage compresses by the specific wiring, while the neck (DN) stage matches the control (61 vs 62) and compresses by convergence alone. Honest breakdown: the raw PR is also pulled by a heavy tail (a few stimuli drive the MN 17× harder than the median), so the direction-only dimension (every response scaled to unit norm) was measured too — real wiring 25 vs control 54 (kWTA), 7 vs 42 (linear). The same formula on Physical AI: joint trajectories of a humanoid robot’s RL walking and running score 1.5–4.1 (median 3.2), dance 9.3, fight 11.9, and evis muscle activations (up to 73 muscles) 5.8–9.6. An order-of-magnitude comparison, not a claim of identity. Raw data and subgraphs are never committed.
↑ The measurement ―― effective dimension of the states each layer takes under 400 random sparse stimuli of the brain layer: real wiring vs a control that keeps every receiver’s input weights but shuffles who sends them; the neck (DN) compresses by convergence alone, the VNC -> MN stage compresses by the specific wiring (figure labels are in Japanese; the numbers are the same)
↑ the same funnel after every stimulus response is scaled to unit norm (magnitude removed, direction kept): the real wiring still leaves fewer MN direct…
↑ the 400 stimuli projected on the first two principal components of the MN states: real wiring folds them onto a few directions, the shuffled control s…
↑ participation ratio of joint-angle trajectories (a humanoid robot, RL policies and mocap retargets), next to the fly’s MN command dimension (2.4) unde…
↑ every number, with the bar it had to clear
py -3.11 examples/poc_connectome_motor_bottleneck.py
Source: examples/poc_connectome_motor_bottleneck.py
This run produced 6 figures in total - see them all
Ops used (notes): graph_block_shuffle · graph_layer_propagate · points_activity_video · states_layer_dimension · states_participation_ratio
MICrONS (about 1 mm^3 of mouse V1 plus higher visual areas) is the rare dataset that holds both the electron-microscopy wiring and the two-photon activity of the same neurons. The public tables of Ding et al. 2025 (Nature) — 12,894 neurons with soma position, visual area and a 120-frame trial-averaged response to a natural movie, and 1.69 M pairs from 148 proofread axons (Connected 8,128 / ADP = axon and dendrite touch without a synapse 287 k / Same region 1.40 M) — are read without committing the raw data or the subgraph. The measured responses are shown as they are, a brain wave over the 1 mm^3 turned by points_activity_video (a cell lights only when it is in its own top quartile), and three rulers measure how much of that wave the wiring explains. (1) Like-to-like reproduced: signal correlation is Connected 0.071 > ADP 0.045 > Same region 0.025, and against a permutation null that shuffles the labels within each axon (sd 0.0018) the difference of 0.027 is 15 sd; ADP and Connected pairs have the same soma distance (289 vs 295 um), so ADP is the distance-matched control. (2) Wiring adds to proximity: the correlation of each axon’s response with the mean response of its partners (a similarity on the same 120 frames, not a held-out prediction) is 0.24 for the connected ones, 0.18 for the same number of touching-but-unconnected ones and 0.12 for the same number of same-region ones; per axon, the connected partners win 73 % of the time. (3) The wave on the wiring: the 148 measured responses driven through the conngraph reservoir of a 4,096-node subgraph (269 axon-to-axon edges dropped) give a correlation of 0.085 between the one-step-delayed target states and the measured responses, above all 20 degree-preserving shuffles (mean 0.048 ± 0.002, max 0.053), with the same order at 0.3× and 3× gain. Honest breakdown: the absolute value is small — a one-hop graph with 1.8 inputs per target from 148 proofread axons — and the spread of the wave (mean distance from the axons 316 um) equals the control: spatial locality is already in the candidate set (ADP), not in who gets chosen. Without the data a synthetic cortex (8 latent signals plus like-to-like wiring among neighbours) runs the same steps and only the orderings are asserted.
↑ The MICrONS Brain Wave — How Much of the Measured Response Does the Wiring Explain in 1 mm^3 of Visual Cortex ―― MICrONS (about 1 mm^3 of mouse V1 plus higher visual areas) is the rare dataset that holds both the electron-microscopy wiring and the two-photon activity of the same neurons. The public tables of Ding et al. 2025 (Nature) — 12,894 neurons with soma position, visual area and a 120-frame trial-averaged response to a natural movie, and 1.69 M pairs from 148 proofread axons (Connected 8,128 / ADP = axon and dendrite touch without a synapse 287 k / Same region 1.40 M) — are read without committing the raw data or the subgraph. The measured responses are shown as they are, a brain wave over the 1 mm^3 turned by points_activity_video (a cell lights only when it is in its own top quartile), and three rulers measure how much of that wave the wiring explains. (1) Like-to-like reproduced: signal correlation is Connected 0.071 > ADP 0.045 > Same region 0.025, and against a permutation null that shuffles the labels within each axon (sd 0.0018) the difference of 0.027 is 15 sd; ADP and Connected pairs have the same soma distance (289 vs 295 um), so ADP is the distance-matched control. (2) Wiring adds to proximity: the correlation of each axon’s response with the mean response of its partners (a similarity on the same 120 frames, not a held-out prediction) is 0.24 for the connected ones, 0.18 for the same number of touching-but-unconnected ones and 0.12 for the same number of same-region ones; per axon, the connected partners win 73 % of the time. (3) The wave on the wiring: the 148 measured responses driven through the conngraph reservoir of a 4,096-node subgraph (269 axon-to-axon edges dropped) give a correlation of 0.085 between the one-step-delayed target states and the measured responses, above all 20 degree-preserving shuffles (mean 0.048 ± 0.002, max 0.053), with the same order at 0.3× and 3× gain. Honest breakdown: the absolute value is small — a one-hop graph with 1.8 inputs per target from 148 proofread axons — and the spread of the wave (mean distance from the axons 316 um) equals the control: spatial locality is already in the candidate set (ADP), not in who gets chosen. Without the data a synthetic cortex (8 latent signals plus like-to-like wiring among neighbours) runs the same steps and only the orderings are asserted.
↑ The measurement ―― signal correlation of the in vivo responses: pairs in the same region binned by soma distance (curve), against the means of the connected pairs and of the ADP pairs whose axon and dendrite touch without a synapse; the per-axon permutation null of Connected - ADP has sd 0.0018 (figure labels are in Japanese; the numbers are the same)
↑ each axon’s response predicted from the mean response of its connected partners (y) versus the same number of touching-but-unconnected partners (x): a…
↑ every number, with the bar it had to clear
↑ The animation ―― the measured responses of the 148 proofread axons (pale yellow) driven through the reservoir of the 4096-node connected subgraph over all somata (grey): a target lights when the drive it receives through its real synapses is in its own top quartile; correlation of the reservoir states with the measured responses 0.085 vs 0.048 for a degree-preserving shuffle
py -3.11 examples/poc_microns_brain_wave.py
Source: examples/poc_microns_brain_wave.py
This run produced 5 figures in total - see them all
Ops used (notes): graph_activity_spread · graph_degree_preserving_shuffle · graph_from_synapses · points_activity_video · reservoir_states
A neuron cut out of serial EM sections becomes a graph of branches once skeletonised, but the per-branch volume and radius that morphometry needs (the compartment parameters of cable theory) can only be counted after every voxel of the volume is told which branch it belongs to. A distance transform returns how far the nearest skeleton is (a value) and discards which skeleton voxel is nearest (a direction), so on its own it cannot draw branch territories. On a synthetic dendrite with true branch labels (5 branches of different radius, one detached piece, 6 debris blobs), the pieces are held per component by vol_rle_components and selected by volume (8 components, 2 kept, 6 debris dropped), the branch ids of skeletonize_vol -> skeleton_branches3d are placed on the skeleton, and vol_nearest_label hands every voxel the id of its nearest branch (a Voronoi partition) which is then cut to the piece: the territories agree with the true nearest axis on 0.978 of the voxels and the per-branch volumes are within 2.9 %. The radius from vol_nearest_seed_vector (surface-to-skeleton displacement) plus 0.5 (the surface voxel centre sits half a voxel inside the boundary) is within 0.24 voxel for every branch, the same accuracy as the classic distance transform on the skeleton (0.23 voxel), but this one gives a radius at every surface voxel. The value-only classic (carve balls around the junctions, then connected components) either fails to separate the branches when the ball is small (agreement 0.63-0.65 for r = 0-4) or throws volume away when it is large (0.78 at r = 6 with 22 % unassigned) - the evidence that the direction is needed. Honest breakdown: skeleton branches end at every junction, so the trunk becomes three branch ids (9 ids onto 6 true branches, matched by majority overlap), and the +0.5 on the radius is a known discretisation bias added explicitly.
↑ Branch Territories — Handing Every Voxel Its Nearest Branch, Not Just Its Distance, Makes Per-Branch Volume and Radius Countable ―― A neuron cut out of serial EM sections becomes a graph of branches once skeletonised, but the per-branch volume and radius that morphometry needs (the compartment parameters of cable theory) can only be counted after every voxel of the volume is told which branch it belongs to. A distance transform returns how far the nearest skeleton is (a value) and discards which skeleton voxel is nearest (a direction), so on its own it cannot draw branch territories. On a synthetic dendrite with true branch labels (5 branches of different radius, one detached piece, 6 debris blobs), the pieces are held per component by vol_rle_components and selected by volume (8 components, 2 kept, 6 debris dropped), the branch ids of skeletonize_vol -> skeleton_branches3d are placed on the skeleton, and vol_nearest_label hands every voxel the id of its nearest branch (a Voronoi partition) which is then cut to the piece: the territories agree with the true nearest axis on 0.978 of the voxels and the per-branch volumes are within 2.9 %. The radius from vol_nearest_seed_vector (surface-to-skeleton displacement) plus 0.5 (the surface voxel centre sits half a voxel inside the boundary) is within 0.24 voxel for every branch, the same accuracy as the classic distance transform on the skeleton (0.23 voxel), but this one gives a radius at every surface voxel. The value-only classic (carve balls around the junctions, then connected components) either fails to separate the branches when the ball is small (agreement 0.63-0.65 for r = 0-4) or throws volume away when it is large (0.78 at r = 6 with 22 % unassigned) - the evidence that the direction is needed. Honest breakdown: skeleton branches end at every junction, so the trunk becomes three branch ids (9 ids onto 6 true branches, matched by majority overlap), and the +0.5 on the radius is a known discretisation bias added explicitly.
↑ The measurement ―― z projection of the synthetic dendrite: every voxel gets the branch whose skeleton is nearest, so the branch volumes can be counted; cutting balls around the junctions instead either fails to separate the branches or throws volume away (figure labels are in Japanese; the numbers are the same)
↑ both readings recover the radius of every branch to within half a voxel (the surface voxel centre sits half a voxel inside the boundary, hence the +0.…
↑ volume per branch comes from the territory; the junction-cut rows show that no ball radius separates the branches without throwing volume away
py -3.11 examples/poc_em_branch_territory.py
Source: examples/poc_em_branch_territory.py
This run produced 4 figures in total - see them all
Ops used (notes): identity · points_activity_video · skeleton · skeleton_branches3d · skeleton_graph3d · skeletonize_vol · vol_distance_transform · vol_label · vol_nearest_label · vol_nearest_seed_vector · vol_rle_components · vol_rle_decode · vol_rle_volume
| The chemical-synapse wiring of the C. elegans hermaphrodite (Cook 2019; n=300, | E | =3,669, 98 left/right pairs): the edge-set overlap (Jaccard) between the wiring and the same wiring with every L/R pair swapped. The measured 0.473 sits between perfect symmetry (1.000) and a degree-preserving null that rewires edges while keeping every degree (0.080 ± 0.002, z 159). Ground truth is a counting closed form: in a mirrored synthetic wiring with m edges per side, moving k right-hand edges gives Jaccard = (m−k)/(m+k), and the op matches it down to the integer numerator and denominator at all 16 steps k=0..120. Per-pair asymmetry falls smoothly from 0.7 to 0.15 — the expected “concentration in a few pairs” turned out to be only about twice uniform (top 10 pairs carry 20 %, uniform would be 10 %). Leading pairs: HSN 60 %, PVN 57 %, RMG 53 %, URX 48 %. No data is bundled; without it the example runs on the synthetic mirrored wiring (Jaccard 0.500 = (120−40)/(120+40)). |
| *↑ Is the Worm’s Wiring Bilaterally Symmetric? — The L/R-Swap Jaccard (set-overlap coefficient), Bracketed by a Closed Form and a Degree-Preserving Null ―― The chemical-synapse wiring of the C. elegans hermaphrodite (Cook 2019; n=300, | E | =3,669, 98 left/right pairs): the edge-set overlap (Jaccard) between the wiring and the same wiring with every L/R pair swapped. The measured 0.473 sits between perfect symmetry (1.000) and a degree-preserving null that rewires edges while keeping every degree (0.080 ± 0.002, z 159). Ground truth is a counting closed form: in a mirrored synthetic wiring with m edges per side, moving k right-hand edges gives Jaccard = (m−k)/(m+k), and the op matches it down to the integer numerator and denominator at all 16 steps k=0..120. Per-pair asymmetry falls smoothly from 0.7 to 0.15 — the expected “concentration in a few pairs” turned out to be only about twice uniform (top 10 pairs carry 20 %, uniform would be 10 %). Leading pairs: HSN 60 %, PVN 57 %, RMG 53 %, URX 48 %. No data is bundled; without it the example runs on the synthetic mirrored wiring (Jaccard 0.500 = (120−40)/(120+40)).* |
Terms: Jaccard — A 0-to-1 set-similarity score equal to intersection size divided by union size.
↑ The measurement ―― C. elegans 雌雄同体・化学シナプス(Cook 2019)。98 組のうち上位 10 組が非対称辺の 20 %(一様なら 10 %)。水平線は全体の Jaccard からの期待率 1 − J。 (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_connectome_lr_symmetry.py
Source: examples/poc_connectome_lr_symmetry.py
This run produced 2 figures in total - see them all
Ops used (notes): graph_degree_summary · graph_swap_symmetry
The chemical-synapse wiring of 8 genetically identical C. elegans (Witvliet 2021, from birth to adulthood), stacked on the 183 cells present in all 8, with graph_edge_consensus counting how many worms carry each connection. 442 connections are present in all 8 worms; a degree-preserving null that rewires every worm independently while keeping each cell’s in- and out-degree gives at most 0 in 20 samples. This core is 15 % of the 2,977 connections in the union but carries 57 % of the synapses summed over the 8 worms. Pairwise Jaccard falls with the difference in estimated age (0.51 on average for neighbouring stages, 0.33-0.34 between the newborn and the adults), and the two same-age adults overlap by only 0.53 - no more than neighbouring stages. Along the developmental order, 701 connections appear and stay to the end while 55 disappear. Ground truth is a closed form on a synthetic series: with a core of C connections in every worm and u unique connections per worm, h[K] = C, h[1] = K*u, every pairwise Jaccard is C/(C+2u), and stable/added/lost/flicker = C/u/u/(K-2)u, matched by the op down to the integer. Against the paper: it calls a connection stable when present in at least 7 datasets and reports about 43 % of adult connections as stable, while a naive cell-level count gives 34.5 %. Matching the authors’ per-connection classification table shows that the paper labels every cell-level connection of a left/right pair stable once the pooled pair connection is present in at least 7 worms (48.9 %); that rule alone reproduces 789 of the paper’s 792 stable connections (99.6 %), and removing variable and dynamic connections first gives 45.4 %. No data is bundled (the nemanode.org data carries no explicit licence); without it the example runs on a synthetic series.
↑ How Much of the Wiring Do Two Genetically Identical Worms Share? — Counting the Connections Present in All 8 Worms of a Developmental Series ―― The chemical-synapse wiring of 8 genetically identical C. elegans (Witvliet 2021, from birth to adulthood), stacked on the 183 cells present in all 8, with graph_edge_consensus counting how many worms carry each connection. 442 connections are present in all 8 worms; a degree-preserving null that rewires every worm independently while keeping each cell’s in- and out-degree gives at most 0 in 20 samples. This core is 15 % of the 2,977 connections in the union but carries 57 % of the synapses summed over the 8 worms. Pairwise Jaccard falls with the difference in estimated age (0.51 on average for neighbouring stages, 0.33-0.34 between the newborn and the adults), and the two same-age adults overlap by only 0.53 - no more than neighbouring stages. Along the developmental order, 701 connections appear and stay to the end while 55 disappear. Ground truth is a closed form on a synthetic series: with a core of C connections in every worm and u unique connections per worm, h[K] = C, h[1] = Ku, every pairwise Jaccard is C/(C+2u), and stable/added/lost/flicker = C/u/u/(K-2)u, matched by the op down to the integer. Against the paper: it calls a connection stable when present in at least 7 datasets and reports about 43 % of adult connections as stable, while a naive cell-level count gives 34.5 %. Matching the authors’ per-connection classification table shows that the paper labels every cell-level connection of a left/right pair stable once the pooled pair connection is present in at least 7 worms (48.9 %); that rule alone reproduces 789 of the paper’s 792 stable connections (99.6 %), and removing variable and dynamic connections first gives 45.4 %. No data is bundled (the nemanode.org data carries no explicit licence); without it the example runs on a synthetic series.*
↑ The measurement ―― 生まれた直後から成虫まで 8 匹の配線を順に。色は全体での出現回数なので、早い段階から在る結合ほど明るい。 (figure labels are in Japanese; the numbers are the same)
↑ ヌルは各個体の入次数・出次数を保ったまま独立に組み替えた配線。
↑ 横軸 0 の点が同齢の成虫 2 匹(Jaccard 0.53)。
↑ 8 匹全員に在る結合は結合数の 15 %、シナプスの 57 %。
↑ 論文は左右の対でまとめた結合が 7 匹以上に在れば、その対の細胞単位の結合すべてに stable の札を付ける(②)。
py -3.11 examples/poc_connectome_across_worms.py
Source: examples/poc_connectome_across_worms.py
This run produced 6 figures in total - see them all
Ops used (notes): graph_edge_consensus · intersection · text_box
The origin of C. elegans wiring diagrams, White 1986 (hand-traced from electron micrographs, adult N2U), stacked with the two adults of Witvliet 2021 on their 215 common cells with graph_edge_consensus. The overlap (Jaccard) is 0.508 between the two adults reconstructed the same way and 0.431 / 0.442 between 1986 N2U and the 2021 adults: the era-and-method difference is 0.07, small next to the individual difference 1 - 0.508 = 0.49. 1,015 connections are present in all three, 36 times the 28 of a degree-preserving null. The 451 connections present in both 2021 adults but missing in 1986 are thin (2.0 synapses on average), clearly separated from those present in all three (5.7). Found by the checks: binning the rounded mean of two animals produced an odd/even zigzag from numpy’s round-half-to-even (2.5 -> 2); counting the integer sum removed it. Honest breakdown: the N2U on nemanode was supplemented with muscles by the Zhen lab in 2020, so the era difference includes re-annotation; JSH is an L4 larva and is left out. No data is bundled; without it the example runs on a synthetic set.
↑ How Much Does a Hand-Traced Wiring Diagram from 40 Years Ago Overlap Today’s Adult Worms? — The Era Difference Next to the Individual Difference ―― The origin of C. elegans wiring diagrams, White 1986 (hand-traced from electron micrographs, adult N2U), stacked with the two adults of Witvliet 2021 on their 215 common cells with graph_edge_consensus. The overlap (Jaccard) is 0.508 between the two adults reconstructed the same way and 0.431 / 0.442 between 1986 N2U and the 2021 adults: the era-and-method difference is 0.07, small next to the individual difference 1 - 0.508 = 0.49. 1,015 connections are present in all three, 36 times the 28 of a degree-preserving null. The 451 connections present in both 2021 adults but missing in 1986 are thin (2.0 synapses on average), clearly separated from those present in all three (5.7). Found by the checks: binning the rounded mean of two animals produced an odd/even zigzag from numpy’s round-half-to-even (2.5 -> 2); counting the integer sum removed it. Honest breakdown: the N2U on nemanode was supplemented with muscles by the Zhen lab in 2020, so the era difference includes re-annotation; JSH is an L4 larva and is left out. No data is bundled; without it the example runs on a synthetic set.
Terms: Jaccard — A 0-to-1 set-similarity score equal to intersection size divided by union size.
↑ The measurement ―― 3 匹すべてに在る結合 1015 本、ヌルの平均 28.2 本。 (figure labels are in Japanese; the numbers are the same)
↑ 2021 の 2 匹ともに在るのに 1986 に無い結合は平均 2.01 シナプス、3 匹とも在る結合は 5.73。
py -3.11 examples/poc_connectome_across_decades.py
Source: examples/poc_connectome_across_decades.py
This run produced 3 figures in total - see them all
Ops used (notes): graph_edge_consensus · intersection
| The neurite skeletons of the 8 C. elegans of Witvliet 2021 (birth to adult), converted to SWC trees and measured with tree_from_swc / tree_morphometry / tree_sholl. 1,727 trees (the 61 skeletons that break into fragments are measured per fragment and summed) all pass the structural promises (one root, parent id < child id, nodes = edges + 1) and the Sholl closed form (area under the curve = sum | d_child - d_parent | ). Second implementation: on the 1,586 single-fragment skeletons the op’s longest path matches the maximum of the authors’ per-node dist_to_root to a relative 1.75e-9. Found by the checks: the authors’ length field is not the sum of segment lengths (median 0.89 of it; 1,360 of 1,586 disagree) and cannot be a gate; dist_to_root also lists nodes without coordinates (54 of 196 skeletons in the first animal), so only nodes with coordinates are compared; writing coordinates to SWC with 3 decimals shifted the path by 1.9e-6. Total length grows from 2,806 um at birth to 12,038 um in the adult, 4.29-fold (3.86-fold on the 195 cells present in all 8), against about 5-fold in the paper; summing the authors’ own length field also gives 3.95-fold, so the 5-fold itself does not come out of a plain sum of this file (the order of magnitude agrees). The L3 total is close to L2 because of specimen shrinkage, which the authors correct by 1.1. No data is bundled. |
| *↑ How Much Do a Worm’s Neurites Grow from Birth to Adulthood? — Measuring Eight Animals’ Skeletons with the Tree Ops and Setting the Result Beside the Paper ―― The neurite skeletons of the 8 C. elegans of Witvliet 2021 (birth to adult), converted to SWC trees and measured with tree_from_swc / tree_morphometry / tree_sholl. 1,727 trees (the 61 skeletons that break into fragments are measured per fragment and summed) all pass the structural promises (one root, parent id < child id, nodes = edges + 1) and the Sholl closed form (area under the curve = sum | d_child - d_parent | ). Second implementation: on the 1,586 single-fragment skeletons the op’s longest path matches the maximum of the authors’ per-node dist_to_root to a relative 1.75e-9. Found by the checks: the authors’ length field is not the sum of segment lengths (median 0.89 of it; 1,360 of 1,586 disagree) and cannot be a gate; dist_to_root also lists nodes without coordinates (54 of 196 skeletons in the first animal), so only nodes with coordinates are compared; writing coordinates to SWC with 3 decimals shifted the path by 1.9e-6. Total length grows from 2,806 um at birth to 12,038 um in the adult, 4.29-fold (3.86-fold on the 195 cells present in all 8), against about 5-fold in the paper; summing the authors’ own length field also gives 3.95-fold, so the 5-fold itself does not come out of a plain sum of this file (the order of magnitude agrees). The L3 total is close to L2 because of specimen shrinkage, which the authors correct by 1.1. No data is bundled.* |
↑ The measurement ―― 同じ名前のニューロン AVAL を 4 つの発生段階で。3-D の Sholl なので回転に依らない。 (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_worm_neurites_grow.py
Source: examples/poc_worm_neurites_grow.py
This run produced 2 figures in total - see them all
Ops used (notes): intersection · tree_from_swc · tree_morphometry · tree_sholl
The errors of automatic neuron segmentation for connectomics are of two kinds: a split cuts one neuron into two, a merge glues two neurons into one. seg_variation_of_information returns VOI divided into split and merge. Injecting one error at a time into the ground truth of CREMI sample A (z = 40, 512 squared) with the existing seg_inject_split / seg_inject_merge, the 4 splits raise only split and the 4 merges only merge, each by exactly (m/N)·H2(m1/m) bits (error < 1e-12; exact for any division of real data). A classic segmentation (membrane response -> threshold -> connected components -> membrane pixels assigned to the nearest cell) goes from over-split at the 60th percentile (split 1.14 / merge 0.18) to over-merged at the 85th (split 0.05 / merge 5.02), with the lowest VOI (1.263) at the 65th, just before the crossing. Found by the checks: leaving membrane pixels as background (label 0) counts the whole background as one huge region and inflates merge 2.4-fold (2.07 against 0.85 at the 70th percentile), making every threshold look over-merged. scikit-image, the second implementation, agrees on the adapted Rand error, but its precision is divided by the truth pairs, the opposite of its docstring. Raw data is not bundled.
↑ Scoring Neuron Segmentations from Electron Microscopy — Over-Splitting and Over-Merging as Two Separate Numbers ―― The errors of automatic neuron segmentation for connectomics are of two kinds: a split cuts one neuron into two, a merge glues two neurons into one. seg_variation_of_information returns VOI divided into split and merge. Injecting one error at a time into the ground truth of CREMI sample A (z = 40, 512 squared) with the existing seg_inject_split / seg_inject_merge, the 4 splits raise only split and the 4 merges only merge, each by exactly (m/N)·H2(m1/m) bits (error < 1e-12; exact for any division of real data). A classic segmentation (membrane response -> threshold -> connected components -> membrane pixels assigned to the nearest cell) goes from over-split at the 60th percentile (split 1.14 / merge 0.18) to over-merged at the 85th (split 0.05 / merge 5.02), with the lowest VOI (1.263) at the 65th, just before the crossing. Found by the checks: leaving membrane pixels as background (label 0) counts the whole background as one huge region and inflates merge 2.4-fold (2.07 against 0.85 at the 70th percentile), making every threshold look over-merged. scikit-image, the second implementation, agrees on the adapted Rand error, but its precision is divided by the truth pairs, the opposite of its docstring. Raw data is not bundled.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_em_split_merge_score.py
Source: examples/poc_em_split_merge_score.py
This run produced 2 figures in total - see them all
Ops used (notes): blob_label · seg_inject_merge · seg_inject_split · seg_label_changes · seg_membrane_response · seg_rand · seg_variation_of_information
A connectome is read by dropping synapse annotations (a pre and a post point) onto segmented neurons, so segmentation errors turn into wiring errors — but not all of them. seg_wiring_variation retakes the pixel VOI at the 2n synapse ends only. This construction is the synapse VI of Plaza et al. 2014 (Focused proofreading); what is new here is the numpy-only implementation, the connection level, the injection experiment and the proofreading-order comparison. On the ground truth of CREMI sample A (125 slices × 625 squared, 115 synapses, 107 connections), one error at a time was injected into each of the 54 neurons carrying synapses: cut in half at the median x, or glued to the neighbour it touches most. The ends VOI matched the closed form (s/2n)·H2(s1/s) in all 108 cases (largest error 1.4e-17). 23 of the 54 cuts change the wiring by not a single bit (no synapse end on one side of the cut). Wiring damage per pixel bit (median) is 1.30 for merges and 0.58 for splits; the rank correlation of pixel and ends VOI is 0.60. Fixing the top 20 errors by pixel VOI removes 41 % of the wiring damage — far better than random (19 %) but short of the wiring order (49 %). Found by the checks: computing a conditional entropy as H(a,b) − H(a) left 8.9e-16 of rounding dust on a pure relabelling and failed the ‘identical means 0’ gate; the direct sum −Σ p log2(n_ij / n_i) makes it exactly 0. VOI over synapses grouped by connection alone stays 0 when a one-synapse connection is cut (most of the 107), so the ends level is primary. The same errors are also scored by NRI (Reilly 2018, the F-score of pairs of ends; the new op seg_synapse_nri, exactly 1 − adapted Rand error over the ends), with the gate that an error costing 0 ends-VOI bits also costs 0 NRI. The rank correlation of NRI loss with ends VOI is 1.00, with pixel VOI 0.59. Inside 1.30 / 0.58: each error’s wiring / pixel ratio is identically density × balance (a gate on all 108). Merges have balance ≈ 1 so their ratio is the density; the cut discount comes mostly from how few ends a neuron has (median 3: even evenly spread ends give an expected H2 of 0.62 by the binomial closed form, measured 0.53). The new op seg_wiring_exposure returns the count-only prediction per neuron (bound s/2n, binomial expectation); over the 54 cuts 0.677 measured / 0.788 predicted / 1.000 bound. Raw data is not bundled.
↑ Where Segmentation Errors Break the Wiring Diagram — Pixel Scores Overweight Splits and Underweight Merges ―― A connectome is read by dropping synapse annotations (a pre and a post point) onto segmented neurons, so segmentation errors turn into wiring errors — but not all of them. seg_wiring_variation retakes the pixel VOI at the 2n synapse ends only. This construction is the synapse VI of Plaza et al. 2014 (Focused proofreading); what is new here is the numpy-only implementation, the connection level, the injection experiment and the proofreading-order comparison. On the ground truth of CREMI sample A (125 slices × 625 squared, 115 synapses, 107 connections), one error at a time was injected into each of the 54 neurons carrying synapses: cut in half at the median x, or glued to the neighbour it touches most. The ends VOI matched the closed form (s/2n)·H2(s1/s) in all 108 cases (largest error 1.4e-17). 23 of the 54 cuts change the wiring by not a single bit (no synapse end on one side of the cut). Wiring damage per pixel bit (median) is 1.30 for merges and 0.58 for splits; the rank correlation of pixel and ends VOI is 0.60. Fixing the top 20 errors by pixel VOI removes 41 % of the wiring damage — far better than random (19 %) but short of the wiring order (49 %). Found by the checks: computing a conditional entropy as H(a,b) − H(a) left 8.9e-16 of rounding dust on a pure relabelling and failed the ‘identical means 0’ gate; the direct sum −Σ p log2(n_ij / n_i) makes it exactly 0. VOI over synapses grouped by connection alone stays 0 when a one-synapse connection is cut (most of the 107), so the ends level is primary. The same errors are also scored by NRI (Reilly 2018, the F-score of pairs of ends; the new op seg_synapse_nri, exactly 1 − adapted Rand error over the ends), with the gate that an error costing 0 ends-VOI bits also costs 0 NRI. The rank correlation of NRI loss with ends VOI is 1.00, with pixel VOI 0.59. Inside 1.30 / 0.58: each error’s wiring / pixel ratio is identically density × balance (a gate on all 108). Merges have balance ≈ 1 so their ratio is the density; the cut discount comes mostly from how few ends a neuron has (median 3: even evenly spread ends give an expected H2 of 0.62 by the binomial closed form, measured 0.53). The new op seg_wiring_exposure returns the count-only prediction per neuron (bound s/2n, binomial expectation); over the 54 cuts 0.677 measured / 0.788 predicted / 1.000 bound. Raw data is not bundled.
↑ The measurement ―― 1 点 = 仕込んだ誤り 1 件。順位相関 0.60。分断の 23 / 54 件は配線を 1 ビットも変えない(横軸の上に並ぶ)。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ CREMI sample A(z 125 枚 × xy 625² @ 0,625)。
py -3.11 examples/poc_em_wiring_errors.py
Source: examples/poc_em_wiring_errors.py
This run produced 4 figures in total - see them all
Ops used (notes): seg_synapse_nri · seg_synapse_partners · seg_variation_of_information · seg_wiring_exposure · seg_wiring_variation
Witvliet 2021’s eight animals carry both a skeleton (shape) and a wiring diagram (synapses) per individual. From the total neurite length measured by the tree ops and the total number of chemical synapses, density rises ×1.32 from 0.462 /µm at birth to 0.611 at 16 h of L1, and afterwards wobbles (max / min) by 1.14 — consistent with the paper’s “density is maintained except during L1” (the gate is “the L1 rise exceeds the later wobble”, with no number hard-coded). Cell by cell, over the 178 cells with a skeleton and synapses in both animals 1 and 8, the rank correlation between neurite growth (median ×3.7) and synapse gain (median ×6.0) is 0.23 (97.5th percentile of a cell-shuffling null: 0.17): not zero, but shape alone does not decide it. Density rose in 82 % of cells. Comparing the matrices of animals 1 and 8 with the new op graph_strength_growth, of the 6,674 new synapses 3,232 thickened existing connections, 3,627 made new ones, −159 were on connections that vanished and −26 on ones that thinned (the four sum exactly to 6,674). The rank correlation between partners at birth and the gain is 0.58 for inputs / 0.51 for outputs, and the top-decile hubs’ share falls from 34 % to 28 % of inputs and 24 % to 20 % of outputs — “hubs disproportionately add inputs” does not appear under this definition (the paper’s quantity is defined differently, so this is not a contradiction). Raw data is not bundled.
↑ Do Synapses Grow in Proportion to Neurites? — Shape Growth and Wiring Growth Set Side by Side, Cell by Cell, in Eight Worms ―― Witvliet 2021’s eight animals carry both a skeleton (shape) and a wiring diagram (synapses) per individual. From the total neurite length measured by the tree ops and the total number of chemical synapses, density rises ×1.32 from 0.462 /µm at birth to 0.611 at 16 h of L1, and afterwards wobbles (max / min) by 1.14 — consistent with the paper’s “density is maintained except during L1” (the gate is “the L1 rise exceeds the later wobble”, with no number hard-coded). Cell by cell, over the 178 cells with a skeleton and synapses in both animals 1 and 8, the rank correlation between neurite growth (median ×3.7) and synapse gain (median ×6.0) is 0.23 (97.5th percentile of a cell-shuffling null: 0.17): not zero, but shape alone does not decide it. Density rose in 82 % of cells. Comparing the matrices of animals 1 and 8 with the new op graph_strength_growth, of the 6,674 new synapses 3,232 thickened existing connections, 3,627 made new ones, −159 were on connections that vanished and −26 on ones that thinned (the four sum exactly to 6,674). The rank correlation between partners at birth and the gain is 0.58 for inputs / 0.51 for outputs, and the top-decile hubs’ share falls from 34 % to 28 % of inputs and 24 % to 20 % of outputs — “hubs disproportionately add inputs” does not appear under this definition (the paper’s quantity is defined differently, so this is not a contradiction). Raw data is not bundled.
↑ The measurement ―― 1 点 = 細胞 178 個。順位相関 0.23(零分布 97.5 % 点 0.17)。 (figure labels are in Japanese; the numbers are the same)
↑ 順位相関 入力 0.58 / 出力 0.51。
py -3.11 examples/poc_worm_synapses_vs_neurites.py
Source: examples/poc_worm_synapses_vs_neurites.py
This run produced 3 figures in total - see them all
Ops used (notes): graph_strength_growth · tree_from_swc · tree_morphometry
| Automatic segmentations are scored either by VOI (split / merge, from the contingency table) or by ERL (expected run length, Januszewski 2018): how far along the ground-truth skeleton one can travel inside one object. The new op tree_run_length counts the runs of a skeleton painted with candidate labels and treats runs of merged objects as length 0. With Witvliet 2021’s 1,713 skeletons (12+ nodes) as ground truth, one candidate error at a time was injected (the candidates are synthetic, not a segmenter’s output). One cut: ERL matches the closed form (A² + (L − A − | e | )²)/L (A = cable of the cut-off subtree, counted by a second traversal) on all 1,713 skeletons (relative 4e-15), and VOI split matches (m/N)·H2 (3.5e-16). On 100 unbranched skeletons, cutting in the middle leaves ERL at 0.48 L, cutting near an end 0.80 L. Gluing the far q of skeleton a onto skeleton b’s object (856 pairs): VOI merge rises monotonically from 0.142 bits at q = 5 % to 0.662 at 50 %, while the ERL of the receiving skeleton b is 0 for every q (loss 1.000) and a’s loss follows the cut formula 1 − (1 − q)². Cutting 65 unbranched skeletons at 3 points uniform along the cable, the run fractions follow Dirichlet(1,…,1): mean Σl²/L’² ÷ 2/(m+2) = 1.023. Found by the checks: the first version claimed that a’s loss was independent of q — wrong; ERL zeroes the runs of the merged object, and the rest of a survives intact; the constant loss falls on b, which that object covers entirely. Sampling cut positions uniformly over node indices instead of cable biased the ratio to 0.945, because edge lengths are uneven. Raw data is not bundled. |
| *↑ Run Length Forgives No Small Merge — ERL and VOI Weigh the Same Error Differently ―― Automatic segmentations are scored either by VOI (split / merge, from the contingency table) or by ERL (expected run length, Januszewski 2018): how far along the ground-truth skeleton one can travel inside one object. The new op tree_run_length counts the runs of a skeleton painted with candidate labels and treats runs of merged objects as length 0. With Witvliet 2021’s 1,713 skeletons (12+ nodes) as ground truth, one candidate error at a time was injected (the candidates are synthetic, not a segmenter’s output). One cut: ERL matches the closed form (A² + (L − A − | e | )²)/L (A = cable of the cut-off subtree, counted by a second traversal) on all 1,713 skeletons (relative 4e-15), and VOI split matches (m/N)·H2 (3.5e-16). On 100 unbranched skeletons, cutting in the middle leaves ERL at 0.48 L, cutting near an end 0.80 L. Gluing the far q of skeleton a onto skeleton b’s object (856 pairs): VOI merge rises monotonically from 0.142 bits at q = 5 % to 0.662 at 50 %, while the ERL of the receiving skeleton b is 0 for every q (loss 1.000) and a’s loss follows the cut formula 1 − (1 − q)². Cutting 65 unbranched skeletons at 3 points uniform along the cable, the run fractions follow Dirichlet(1,…,1): mean Σl²/L’² ÷ 2/(m+2) = 1.023. Found by the checks: the first version claimed that a’s loss was independent of q — wrong; ERL zeroes the runs of the merged object, and the rest of a survives intact; the constant loss falls on b, which that object covers entirely. Sampling cut positions uniformly over node indices instead of cable biased the ratio to 0.945, because edge lengths are uneven. Raw data is not bundled.* |
↑ The measurement ―― 1 点 = 骨格 1 本。真ん中で切ると ERL は L の約 1/2、端で切ると約 0.8 L。 (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_skeleton_run_length_vs_voi.py
Source: examples/poc_skeleton_run_length_vs_voi.py
This run produced 2 figures in total - see them all
Ops used (notes): seg_variation_of_information · tree_from_swc · tree_run_length
| The slime mould Physarum leaves the shortest path of a maze by nothing more than thickening and thinning its tubes with the flow through them (Tero 2010). Its dynamics (Kirchhoff pressures, dD/dt = | Q | − D) are proved to converge to the indicator of the shortest path when it is unique (Bonifaci 2012). Two new ops run it — graph_physarum_path on a weighted graph and physarum_route on a cost image (neighbouring pixels joined by a tube of length (c_u+c_v)/2) — with Dijkstra (scipy) and route_through_array (skimage) as the truth. On five 15×15 lattices, a 21×21 perfect maze and a 32×32 terrain the mould’s route equals the minimum-cost path in every case (difference < 1e-9). At 600 iterations the lattice routes already match 5/5 (the Afterman PoC had 4/5); by 3,000 iterations 3/5 have converged to the indicator (> 0.99 on the path, < 0.01 elsewhere) and the two cut off are those whose gap to the runner-up route (the exact second-shortest, from Dijkstra with each edge of the shortest path removed in turn) is smallest, 0.009 and 0.032 — the gap sets the speed. The Lyapunov function V = Σ L·D fell in the maze from 227,280 to 133.60 (shortest path 133.59) without rising once over 23 intervals, and the inequality flow length Σ | Q | L ≥ shortest path held exactly throughout. Where many routes tie (uniform cost) the tubes never settle on one, and the op refuses. |
| *↑ Slime-Mould Tubes Solve the Maze — Thickening and Thinning Alone Converge to the Shortest Path, Bracketed by a Theorem and Dijkstra ―― The slime mould Physarum leaves the shortest path of a maze by nothing more than thickening and thinning its tubes with the flow through them (Tero 2010). Its dynamics (Kirchhoff pressures, dD/dt = | Q | − D) are proved to converge to the indicator of the shortest path when it is unique (Bonifaci 2012). Two new ops run it — graph_physarum_path on a weighted graph and physarum_route on a cost image (neighbouring pixels joined by a tube of length (c_u+c_v)/2) — with Dijkstra (scipy) and route_through_array (skimage) as the truth. On five 15×15 lattices, a 21×21 perfect maze and a 32×32 terrain the mould’s route equals the minimum-cost path in every case (difference < 1e-9). At 600 iterations the lattice routes already match 5/5 (the Afterman PoC had 4/5); by 3,000 iterations 3/5 have converged to the indicator (> 0.99 on the path, < 0.01 elsewhere) and the two cut off are those whose gap to the runner-up route (the exact second-shortest, from Dijkstra with each edge of the shortest path removed in turn) is smallest, 0.009 and 0.032 — the gap sets the speed. The Lyapunov function V = Σ L·D fell in the maze from 227,280 to 133.60 (shortest path 133.59) without rising once over 23 intervals, and the inequality flow length Σ | Q | L ≥ shortest path held exactly throughout. Where many routes tie (uniform cost) the tubes never settle on one, and the op refuses.* |
↑ The measurement ―― 同じ迷路、24 コマ。行き止まりから順に細り、最後に最短路だけが残る。 (figure labels are in Japanese; the numbers are the same)
↑ 32×32。
↑ 15×15 の格子、辺長 U(0.5, 1.5)、左上 → 右下。
py -3.11 examples/poc_physarum_maze.py
Source: examples/poc_physarum_maze.py
This run produced 4 figures in total - see them all
Ops used (notes): graph_physarum_path · physarum_route
| The maze PoC had one source and one sink. Give the same dynamics (Kirchhoff pressures, Q = D (p_u − p_v)/L, dD/dt = | Q | − D) a supply vector (sum 0) instead, and it converges to L1 optimal transport on the graph — the Beckmann problem, i.e. the 1-Wasserstein distance (Bonifaci 2017; Facca, Karrenbauer, Kolev and Mehlhorn 2020; Facca, Cardin and Putti 2018 for the continuum). Two new ops, graph_physarum_transport (weighted graph + supply) and physarum_transport_image (two mass images, pixels joined by unit tubes = EMD in the Manhattan metric), return the cost Σ L | Q | (an upper bound) together with the Kantorovich–Rubinstein lower bound — b·φ for the McShane envelope φ of the pressure, made 1-Lipschitz. The true distance always lies between them, so the gap certifies how far the current flow is from optimal, and the run stops when it closes. Ten truths: on five random trees the closed form Σ L_e | subtree supply | matches to 1.7e-15 (the flow on a tree is unique; 37–38 iterations); on an uneven 1-D grid the existing op wasserstein_1d agrees at 0.316477; on a 12-vs-12 complete bipartite graph the Hungarian minimum assignment 0.163242 is met at 0.163244 (459 iterations) and the surviving tubes are exactly the optimal assignment (≥ 1.00 on it, ≤ 0.001 elsewhere); on a 6×6 grid of random mass the LP (HiGHS) optimum 0.591849 is matched; a disk shifted by (5, 8) pixels gives EMD 13.000086 = | dr | + | dc | as the theorem says; with one source and one sink the lower bound equals Dijkstra’s distance 9.367260049 exactly (the envelope of the pressure is the shortest-path potential). The metric axioms (symmetry, linearity in length and mass, triangle inequality) hold. Disk → two disks (24×24) reaches EMD 12.0001 in 183 iterations, filmed in 36 frames as the tubes form. 9.1 s in all. Honestly: the mould is not faster than the Hungarian algorithm or the network simplex. Its merits are that a local rule alone reaches the optimum, that it batches in parallel, and that it certifies its own answer. |
| *↑ Slime Mould Solves Optimal Transport — Make the Source and Sink Mass Distributions and the Same Tube Dynamics Converge to the Earth Mover’s Distance ―― The maze PoC had one source and one sink. Give the same dynamics (Kirchhoff pressures, Q = D (p_u − p_v)/L, dD/dt = | Q | − D) a supply vector (sum 0) instead, and it converges to L1 optimal transport on the graph — the Beckmann problem, i.e. the 1-Wasserstein distance (Bonifaci 2017; Facca, Karrenbauer, Kolev and Mehlhorn 2020; Facca, Cardin and Putti 2018 for the continuum). Two new ops, graph_physarum_transport (weighted graph + supply) and physarum_transport_image (two mass images, pixels joined by unit tubes = EMD in the Manhattan metric), return the cost Σ L | Q | (an upper bound) together with the Kantorovich–Rubinstein lower bound — b·φ for the McShane envelope φ of the pressure, made 1-Lipschitz. The true distance always lies between them, so the gap certifies how far the current flow is from optimal, and the run stops when it closes. Ten truths: on five random trees the closed form Σ L_e | subtree supply | matches to 1.7e-15 (the flow on a tree is unique; 37–38 iterations); on an uneven 1-D grid the existing op wasserstein_1d agrees at 0.316477; on a 12-vs-12 complete bipartite graph the Hungarian minimum assignment 0.163242 is met at 0.163244 (459 iterations) and the surviving tubes are exactly the optimal assignment (≥ 1.00 on it, ≤ 0.001 elsewhere); on a 6×6 grid of random mass the LP (HiGHS) optimum 0.591849 is matched; a disk shifted by (5, 8) pixels gives EMD 13.000086 = | dr | + | dc | as the theorem says; with one source and one sink the lower bound equals Dijkstra’s distance 9.367260049 exactly (the envelope of the pressure is the shortest-path potential). The metric axioms (symmetry, linearity in length and mass, triangle inequality) hold. Disk → two disks (24×24) reaches EMD 12.0001 in 183 iterations, filmed in 36 frames as the tubes form. 9.1 s in all. Honestly: the mould is not faster than the Hungarian algorithm or the network simplex. Its merits are that a local rule alone reaches the optimum, that it batches in parallel, and that it certifies its own answer.* |
↑ The measurement ―― EMD 12.0001(画素単位)。粘菌の費用 12.0001、Kantorovich–Rubinstein の下界 12.0000。 (figure labels are in Japanese; the numbers are the same)
↑ 完全 2 部グラフ 144 本、ユークリッド長。
↑ 円盤 → 2 円盤。
↑ 木 seed 0 16.34; 木 seed 1 13.71; 木 seed 2 14.19; 木 seed 3 15.71; 木 seed 4 14.44; 1 次元 0.3165; 割当 12×12 0.1632; 格子 6×6 LP 0.5918; 平行移動 (5, 8) 13; 単一対 7×…
py -3.11 examples/poc_physarum_transport.py
Source: examples/poc_physarum_transport.py
This run produced 5 figures in total - see them all
Ops used (notes): graph_physarum_transport · physarum_transport_image · wasserstein_1d
The vessel-network PoC used a synthetic tree as truth; here the truth is a real tree — NeuroMorpho.Org SWC files (coordinates, radius and parent id per node; three mouse neocortical neurons, CC BY 4.0). SWC carries structural constraints (exactly one root, parent id < child id, nodes = edges + 1) that are gates in themselves: a broken tree is not graded. The 3-D Sholl count (intersections of branches with spheres around the soma) depends only on distances from the origin, so across 12 rotations not a single integer moves. Image measurement, however, runs on a projection — the projected Sholl count moves by up to 9–16 intersections with viewing angle, and the bifurcation count against ground truth 16 / 11 / 37 becomes 22–28 / 15–24 / 41–49 over 12 viewing angles (the excess is branch crossings that thinning turns into junctions), while skeleton length shrinks to 0.72–0.82 of the cable length. The precision of a “tree measurement” is set by the line of sight, not the tree. No data is bundled; without it the example runs on a synthetic tree that satisfies the same constraints (a tangled 3-D synthetic tree projects worse than the real ones — and says so).
↑ Grading Skeleton Measurement on Real Trees — NeuroMorpho SWC as Ground Truth, and What Projection Breaks ―― The vessel-network PoC used a synthetic tree as truth; here the truth is a real tree — NeuroMorpho.Org SWC files (coordinates, radius and parent id per node; three mouse neocortical neurons, CC BY 4.0). SWC carries structural constraints (exactly one root, parent id < child id, nodes = edges + 1) that are gates in themselves: a broken tree is not graded. The 3-D Sholl count (intersections of branches with spheres around the soma) depends only on distances from the origin, so across 12 rotations not a single integer moves. Image measurement, however, runs on a projection — the projected Sholl count moves by up to 9–16 intersections with viewing angle, and the bifurcation count against ground truth 16 / 11 / 37 becomes 22–28 / 15–24 / 41–49 over 12 viewing angles (the excess is branch crossings that thinning turns into junctions), while skeleton length shrinks to 0.72–0.82 of the cable length. The precision of a “tree measurement” is set by the line of sight, not the tree. No data is bundled; without it the example runs on a synthetic tree that satisfies the same constraints (a tangled 3-D synthetic tree projects worse than the real ones — and says so).
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ 3-D の交点数は 12 回転で整数が 1 つも動かない。
py -3.11 examples/poc_swc_tree_truth.py
Source: examples/poc_swc_tree_truth.py
This run produced 3 figures in total - see them all
Ops used (notes): skeleton · tree_from_swc · tree_morphometry · tree_sholl
The chemical-synapse wirings of Witvliet 2021’s 8 animals (0 h after birth → adult) are peeled with three new ops: graph_kcore (k-core / weighted s-core; in, out, total, undirected), graph_rich_club_curve (the rich-club coefficient for every k, divided by the degree-preserving null) and graph_core_persistence (the cells that stay in the deepest shell of all K animals). Gates are theorems: a complete graph is one shell of index n − 1, a tree 1, a cycle 2; the s-core of a 0/1 matrix equals the k-core exactly; weights × c → indices × c; the undirected k-core agrees with networkx on every node; φ(k) equals the single-k graph_rich_club for every k. Peeled by synapse count, the deepest (in-degree s-core) shell stays at 6–10 cells through development while its index deepens from 7 to 55, and the interneuron pair RIA is in the deepest shell of all 8 (10 motor and 2 interneuron classes; no muscle or glia). Peeled by 0/1, the k-core is coarse even in the adult (index 3–5) and its deepest shell swells to 150 cells. 51 cells persist in at least one of the 4 core types (in / out × k / s) — the published value of Yadav & Singh 2026 (bioRxiv); their ‘only AIBR, RIBL, RIAR among head neurons’ does not match the RIAL, RIAR persistent in all 4 types under my definition, and is recorded as is. A rich-club band exists at every stage (peak ratio 1.4–3.3, k = 1–28 in the adult). Figures: the 8 wirings on the neuron positions (skeleton roots, viewed along the body axis) aligned to the adult by Procrustes, and a moving figure interpolating between stages as connections grow and the core changes hands. ≈ 17 s.
↑ The Worm Brain’s Core Is There from Birth — Counting the Cells That Stay in the Deepest Shell Across 8 Developmental Stages ―― The chemical-synapse wirings of Witvliet 2021’s 8 animals (0 h after birth → adult) are peeled with three new ops: graph_kcore (k-core / weighted s-core; in, out, total, undirected), graph_rich_club_curve (the rich-club coefficient for every k, divided by the degree-preserving null) and graph_core_persistence (the cells that stay in the deepest shell of all K animals). Gates are theorems: a complete graph is one shell of index n − 1, a tree 1, a cycle 2; the s-core of a 0/1 matrix equals the k-core exactly; weights × c → indices × c; the undirected k-core agrees with networkx on every node; φ(k) equals the single-k graph_rich_club for every k. Peeled by synapse count, the deepest (in-degree s-core) shell stays at 6–10 cells through development while its index deepens from 7 to 55, and the interneuron pair RIA is in the deepest shell of all 8 (10 motor and 2 interneuron classes; no muscle or glia). Peeled by 0/1, the k-core is coarse even in the adult (index 3–5) and its deepest shell swells to 150 cells. 51 cells persist in at least one of the 4 core types (in / out × k / s) — the published value of Yadav & Singh 2026 (bioRxiv); their ‘only AIBR, RIBL, RIAR among head neurons’ does not match the RIAL, RIAR persistent in all 4 types under my definition, and is recorded as is. A rich-club band exists at every stage (peak ratio 1.4–3.3, k = 1–28 in the adult). Figures: the 8 wirings on the neuron positions (skeleton roots, viewed along the body axis) aligned to the adult by Procrustes, and a moving figure interpolating between stages as connections grow and the core changes hands. ≈ 17 s.
↑ The measurement ―― 同じ 8 匹。0/1 の k-core は成虫でも指数 5、最深殻が 179 細胞まで膨らむ。シナプス数で剥く s-core は最深殻が 6〜10 細胞のまま深くなる。 (figure labels are in Japanese; the numbers are the same)
↑ 次数保存ヌル 20 標本。
↑ The animation ―― 生後 0 時間から成虫まで。結合が生え、最深殻(橙)が入れ替わる中で、赤の細胞(RIAL RIAR)は一度も外れない。
py -3.11 examples/poc_worm_core_persists.py
Source: examples/poc_worm_core_persists.py
This run produced 4 figures in total - see them all
Ops used (notes): graph_core_persistence · graph_kcore · graph_rich_club · graph_rich_club_curve
From the MaleCNS v1.0 annotations (Janelia FlyEM × Google Research, the whole male CNS, CC BY 4.0) the positions and superclasses of 126,104 brain cell bodies (VNC and ascending excluded) are drawn as a point cloud from the front, side and top and in rotation (optic-lobe intrinsic 81,055, central-brain intrinsic 31,997, visual projection 9,162, descending 1,308, …). Fullseye had no op that turns a point cloud into a colour picture; the brain on the back of the ViEW 2026 business card was placed point by point in hand-written numpy. The new op points_render (ops3d / render) splats sub-pixel footprints (gaussian / disc / point) and either alpha-composites them in depth order or adds them conserving the total light. The gates use values the renderer never computes: 99.5 % of the cell bodies labelled L land in the right half of the front view (seen from the front the fly’s left is on the right); 4,050 Kenyon cells picked by type name sit dorsal and posterior of 437 antennal-lobe neurons; the column sums of the front and top views (two different cameras) agree mirrored to 1.8e-15; the additive image sums to the point count within 1.2e-16; each class’s image centroid lies on the closed-form projection within 1.4e-5 px. The op has 37 unit gates (its z-buffer matches render_point_depth on every pixel; a quarter turn about the axis equals numpy.rot90), and ten deliberate breakages (no depth sort, half-pixel shift, transposed rotation, transmittance off by one, …) all turn them red. Cutting the Gaussian at 3 sigma moved centroids by 0.017 px and a circular cut made column sums depend on the row by 1e-5; the cut is now 4 sigma per axis. Raw data is never committed (cached from local feather files; without them a synthetic brain runs the same gates and the recorded values are printed).
↑ Drawing the Male Fly Brain as 126,104 Cell Bodies — A New Point-Cloud Renderer, Scored Against Four Answers from Outside the Picture ―― From the MaleCNS v1.0 annotations (Janelia FlyEM × Google Research, the whole male CNS, CC BY 4.0) the positions and superclasses of 126,104 brain cell bodies (VNC and ascending excluded) are drawn as a point cloud from the front, side and top and in rotation (optic-lobe intrinsic 81,055, central-brain intrinsic 31,997, visual projection 9,162, descending 1,308, …). Fullseye had no op that turns a point cloud into a colour picture; the brain on the back of the ViEW 2026 business card was placed point by point in hand-written numpy. The new op points_render (ops3d / render) splats sub-pixel footprints (gaussian / disc / point) and either alpha-composites them in depth order or adds them conserving the total light. The gates use values the renderer never computes: 99.5 % of the cell bodies labelled L land in the right half of the front view (seen from the front the fly’s left is on the right); 4,050 Kenyon cells picked by type name sit dorsal and posterior of 437 antennal-lobe neurons; the column sums of the front and top views (two different cameras) agree mirrored to 1.8e-15; the additive image sums to the point count within 1.2e-16; each class’s image centroid lies on the closed-form projection within 1.4e-5 px. The op has 37 unit gates (its z-buffer matches render_point_depth on every pixel; a quarter turn about the axis equals numpy.rot90), and ten deliberate breakages (no depth sort, half-pixel shift, transposed rotation, transmittance off by one, …) all turn them red. Cutting the Gaussian at 3 sigma moved centroids by 0.017 px and a circular cut made column sums depend on the row by 1e-5; the cut is now 4 sigma per axis. Raw data is never committed (cached from local feather files; without them a synthetic brain runs the same gates and the recorded values are printed).
↑ The measurement ―― 回転の 1 コマ目(静止画の代わり)。正面から見たところ。MaleCNS v1.0(CC BY 4.0) (figure labels are in Japanese; the numbers are the same)
↑ points_render のノブ: footprint(point / disc / gaussian)と composite(over / add)。
↑ The animation ―― 背腹の軸まわりに 1 周(36 コマ)。どのコマも points_render の 1 回の呼び出し。MaleCNS v1.0(CC BY 4.0)
py -3.11 examples/poc_flybrain_pointcloud.py
Source: examples/poc_flybrain_pointcloud.py
This run produced 4 figures in total - see them all
Ops used (notes): annotate_figure_grid · annotate_scale_bar · gaussian · points_render · text_box
Stellar brightness and position, the solar limb, all-sky cloud cover, sea-ice concentration, crop cover, terrain, river stage. The subjects are far away and ground truth is normally out of reach. The 21 exhibits here turn that around, placing their truth in closed forms and public data: celestial coordinates, the solid angle of a spherical cap, Eddington limb darkening, elevation tiles from the Geospatial Information Authority of Japan.
The shared finding is that the same object reads differently depending on where it is: the same cloud counts 1.45x more at the horizon than at the zenith; clouds of equal optical thickness are detected or not depending on their angular distance from the sun; the same reflection makes one detector read quietly low and another stop silently.
The other is that the definition of truth decides the conclusion. Counting thin ice as ‘ice’ or not sends the same estimate to -4.4 or +2.7 points; a cloud fraction reported without its horizon-mask angle can legitimately claim anything from 0.18 to 0.23. What you call the truth has to be written down before the instrument is.
One cloud occupies different pixel areas at different positions in an all-sky image. Spherical caps were projected through a fisheye model and counted out to a zenith angle of 82 degrees. Pixel counting increasingly overstates clouds near the horizon, while geometric and detection errors partly cancel.
↑ All-Sky Cloud Cover — Counting Pixels Is Biased by Position ―― Spherical-cap clouds (closed-form solid angle) placed in an equidistant fisheye sky, with cloud fraction counted by pixel ratio and by solid-angle weight. The same cloud reads 0.00789 at the zenith and 0.01142 at 82 degrees (1.45x). Geometry alone errs by -5.02 %, detection alone by +37.95 %, and the naive count cancels them to +29.73 %.
↑ The measurement ―― 画素数比は天頂で 0.81、地平線側で 1.17。重みを掛けると 1 に張り付く。 (figure labels are in Japanese; the numbers are the same)
↑ 偽陽性は実測と閉形式が重なる。
↑ 4 枚目が sinθ/θ。
py -3.11 examples/poc_allsky_cloud_cover.py
Source: examples/poc_allsky_cloud_cover.py
This run produced 4 figures in total - see them all
Ops used (notes): equirect_from_fisheye_pair · polar_trans_image
The question is whether stacking more star fields reduces photometric error at the theoretical rate. Fields with known stellar brightness were stacked until aperture-photometry error fell to 0.1616 %. The expected improvement appears, while rejection averaging stays closer to truth when a cosmic ray is present.
↑ How Many Frames for What Photometric Precision? ―― A star field placed to specification and stacked N deep, to see whether aperture-photometry error falls as 1/√N. From N = 1 to 16 the median error drops from 0.6350 % to 0.1616 %, within 4.6 % of theory in all eight cases. One cosmic ray pushes the plain mean to +5.89 % while κ-σ clipping holds +0.30 %; the rejection rate does not move, so ‘the rejection rate went up, therefore it worked’ is not an available argument.
↑ The measurement ―― 単純平均だけが 5.9 % 残る。κ-σ は汚染なしと区別できないところまで戻すが、棄却率はほとんど動かない。 (figure labels are in Japanese; the numbers are the same)
↑ 開口を広く取れるなら、ぼけたフレームも同じ明るさを持っている(r=12 では 3 本が重なる)。
↑ 悪いほうは星が広がっている。
py -3.11 examples/poc_astro_photometry.py
Source: examples/poc_astro_photometry.py
This run produced 4 figures in total - see them all
Ops used (notes): aperture_photometry · drizzle_resample · lucky_select · median · sigma_clip_stack · synth_frame_series · synth_starfield
In two-date subtraction, unchanged edges can appear as change solely from misregistration. Known land changes were inserted before shifting the images, revealing a false-positive cliff at 0.5 px. Residual alignment error follows the same cliff, which radiometric correction cannot remove.
↑ Change detection under misregistration — false positives are edge bands, with a cliff ―― A two-date synthetic land surface (fields, roads, buildings, forest, lake) with planted changes (new buildings, clear-cut, lake expansion); date 2 is degraded by sub-pixel shift, small rotation and an illumination change, and the false-positive area of plain differencing is measured. False positives stay at the noise floor up to 0.3 px and jump at 0.5 px (matching the PSF-derived onset δ=τσ√2π/C=0.351 px), reaching 7175 px at 3 px. The edge-length × shift rule gives 0.78× at 3 px but cannot explain the cliff; the PSF+noise edge-ledger prediction is within 0.84–1.07×. Three registration paths (PIV, keypoints, LK) bring the residual down to 0.02–0.13 px, but the only phase-correlation path is 3-D and integer-valued (residual 0.72 px), and its 995 px of false positives lands on the shift-only sweep read at the same residual (915 px). Clear-cut recall is 0.33 even with perfect alignment; the illumination-only 17198 px vanish with radiometric normalisation while the 3497 px from shift do not.*
Terms: PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.
↑ The measurement ―― 0.35 px までゼロ、そこから立ち上がる。比例則は崖を説明しない。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 回転 1 度の偽陽性地図(橙)。
↑ この回の図
↑ この回の図
py -3.11 examples/poc_change_detection_misreg.py
Source: examples/poc_change_detection_misreg.py
This run produced 11 figures in total - see them all
Ops used (notes): affine_trans_image · histogram_match · match_phase_3d · opening_circle · piv_cross_correlate · piv_outlier_mask · procrustes_fit · threshold · voxel_iou
A temperature field for a 12 x 8.4 x 3.0 m server room is written as a formula, then reconstructed from a grid of temperature sensors to find three overloaded racks. The null baseline, the mean of all sensors spread over the whole room, gives an RMSE of 2.192 degrees and finds no hot spot at all; the best method, a thin-plate RBF, gives 0.214, ten times better. The cliff can be stated in closed form before measuring: on a grid of spacing d the point furthest from a peak is the cell centre at dsqrt(3)/2, so a Gaussian of width sigma is recovered at exp(-3d^2/8sigma^2) and halves at d = 1.3596 sigma. Isolating the geometry, prediction and measurement differ by at most 1.2e-15. What failed was not the formula but the quantity a site can actually measure: the peak of a reconstructed field carries the background’s reconstruction error at the same place, so a naive measurement exceeds the prediction by up to +0.2598 and the cliff looks shallower than it is. The sigma=0.22 m rack is recovered at 0.000014 at d=1.20 m, effectively gone, yet naively appears to hold 0.1609 — what remains is the background error. The closed form is a floor, not a curve: at the same d=0.60 m, recovery jumps from 0.0615 at a cell centre to 1.0 directly above a sensor, so only the worst phase is usable for design. Random placement does not remove blind spots; it only reshuffles which rack falls into one. Measured, 6.17 % of random points exceed the grid’s worst distance against a Poisson prediction of 6.58 %. The grid never breaks its geometric lower bound while random placement broke it for two of the three racks: with the same sensor count, only one of them supports a guarantee. Swapping the interpolator moves the coefficient, not the exponent — all three slopes of log recovery against d^2 sit within 3.05 % of the predicted -3.0612 — but the prediction that the cliff position is method-independent was wrong: the thin-plate spline overshoots its own nodes by a factor of 1.372 and shifts the half-recovery spacing from 0.4777 to 0.6015 m, a 26 % move. The follow-up prediction, that an overshooting method raises the false peaks by the same factor, was also wrong: the nearest-neighbour floor is 0.975 degrees, 6.5 times the sensor noise, because a staircase approximation of a smooth background is itself a step. No method wins all three rulers at once, and at d=1.20 m linear interpolation falls outside the convex hull for 71.2 % of the evaluation points, silently becoming nearest-neighbour at the walls. The gap this PoC found has since been filled: fs.interp_scattered interpolates a field from scattered N-D points and returns the fraction of query points that fell outside the convex hull — the 71.2 % measured here is exactly the quantity that would otherwise turn silently into NaN or into another method under the first method’s name.
↑ A 3-D Thermal Field from Sparse Sensors — The Grid’s Blind Spot Erases a Rack ―― A temperature field for a 12 x 8.4 x 3.0 m server room is written as a formula, then reconstructed from a grid of temperature sensors to find three overloaded racks. The null baseline, the mean of all sensors spread over the whole room, gives an RMSE of 2.192 degrees and finds no hot spot at all; the best method, a thin-plate RBF, gives 0.214, ten times better. The cliff can be stated in closed form before measuring: on a grid of spacing d the point furthest from a peak is the cell centre at dsqrt(3)/2, so a Gaussian of width sigma is recovered at exp(-3d^2/8sigma^2) and halves at d* = 1.3596 sigma. Isolating the geometry, prediction and measurement differ by at most 1.2e-15. What failed was not the formula but the quantity a site can actually measure: the peak of a reconstructed field carries the background’s reconstruction error at the same place, so a naive measurement exceeds the prediction by up to +0.2598 and the cliff looks shallower than it is. The sigma=0.22 m rack is recovered at 0.000014 at d=1.20 m, effectively gone, yet naively appears to hold 0.1609 — what remains is the background error. The closed form is a floor, not a curve: at the same d=0.60 m, recovery jumps from 0.0615 at a cell centre to 1.0 directly above a sensor, so only the worst phase is usable for design. Random placement does not remove blind spots; it only reshuffles which rack falls into one. Measured, 6.17 % of random points exceed the grid’s worst distance against a Poisson prediction of 6.58 %. The grid never breaks its geometric lower bound while random placement broke it for two of the three racks: with the same sensor count, only one of them supports a guarantee. Swapping the interpolator moves the coefficient, not the exponent — all three slopes of log recovery against d^2 sit within 3.05 % of the predicted -3.0612 — but the prediction that the cliff position is method-independent was wrong: the thin-plate spline overshoots its own nodes by a factor of 1.372 and shifts the half-recovery spacing from 0.4777 to 0.6015 m, a 26 % move. The follow-up prediction, that an overshooting method raises the false peaks by the same factor, was also wrong: the nearest-neighbour floor is 0.975 degrees, 6.5 times the sensor noise, because a staircase approximation of a smooth background is itself a step. No method wins all three rulers at once, and at d=1.20 m linear interpolation falls outside the convex hull for 71.2 % of the evaluation points, silently becoming nearest-neighbour at the walls. The gap this PoC found has since been filled: fs.interp_scattered interpolates a field from scattered N-D points and returns the fraction of query points that fell outside the convex hull — the 71.2 % measured here is exactly the quantity that would otherwise turn silently into NaN or into another method under the first method’s name.*
↑ The measurement ―― 幾何の実測は予測と最大 1.2e-15 しか違わない。素朴な実測が上に浮くぶんが背景の復元誤差。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 格子の最悪距離 0.520 m を乱数の 6.17 % が超えた(予測 6.58 %)。
↑ この回の図
↑ 横=x 0..12 m、縦=y 0..8.4 m。
py -3.11 examples/poc_datacenter_thermal_field.py
Source: examples/poc_datacenter_thermal_field.py
This run produced 8 figures in total - see them all
Ops used (notes): interp_scattered · render_volume_projection · rmse · vol_local_maxima
Slope, curvature and sky-view factor checked against closed forms on a plane, a cone and a Gaussian hill. Halving the cell size cuts the hill’s curvature error by about four — a discretisation error, not a wrong formula. The sky-view factor takes 2.03 s on a 513×513 grid with 8 directions; before the rewrite it took 41.9 s, and the tests had only ever checked that it runs.
↑ Measuring Terrain — Slope, Flow and Insolation Against Closed Forms ―― Slope, curvature and sky-view factor checked against closed forms on a plane, a cone and a Gaussian hill. Halving the cell size cuts the hill’s curvature error by about four — a discretisation error, not a wrong formula. The sky-view factor takes 2.03 s on a 513×513 grid with 8 directions; before the rewrite it took 41.9 s, and the tests had only ever checked that it runs.
↑ The measurement ―― 参照線と平行 = 2 次収束 = 離散化の誤差。式が違えばセルを細かくしても誤差は下げ止まる。 (figure labels are in Japanese; the numbers are the same)
↑ 同じ地形・同じ op。
↑ 陰影の平均は 北向き 0.354 / 東向き 0.811 / 南向き 0.811 / 西向き 0.354。
↑ The animation ―― 主図(動画、640 × 360・30 fps・11 秒): 800 m 四方の地形(セル 5 m)を南南西から北へ回り込み、止まって太陽を一周させる。陰影は描画の光でなく dem_hillshade(仰角 35 度)の出力で塗り、青は dem_flow_accumulation の集水量 150 セル以上。南向き斜面の陰影の平均は太陽方位 187 度で最大 0.719、北向き斜面は 1 度で最大 0.709 —— 日当たりは斜面の向きで決まる(§6 の平面と同じ結論)。高さは画面上だけ 2.5 倍。
py -3.11 examples/poc_dem_terrain.py
Source: examples/poc_dem_terrain.py
This run produced 5 figures in total - see them all
Ops used (notes): color_bar · dem_aspect · dem_curvature · dem_fill_sinks · dem_flow_accumulation · dem_hillshade · dem_sky_view_factor · dem_slope · text_box
A synthetic star field (240 frames) carries a 10 ppt limb-darkened transit on the target only, with transparency variation, sub-pixel drift, flat-field non-uniformity and photon noise injected from separate random streams; fullseye’s star_detect → frame_align → aperture_photometry extracts the light curve. The zero point (target aperture sum alone) breaks under clouds to a depth error of +72 ppt; the ratio to comparison stars gives -0.13 ppt / T14 -0.9 fr. Comparison-star choice moves the residual rms from 1.91 to 11.43 ppt (6.0x), and inverse-variance weights computed from raw variance are fooled by clouds into 1.52x worse than a plain sum. The small-aperture cliff at 1σ was not the predicted centroid error but the op’s aperture-mask staircase (supersample=8: a static star sits 1.69x above theory, 0.93x at 32). The SNR=5 detection limit is 1.48 ppt measured vs 1.45 ppt theory with a known ephemeris, 2.0 ppt blind, where the duration breaks before the depth. Drift 2 px and a 3 % flat are harmless alone (0.16 / 0.08 ppt) but multiply into a 0.94 ppt false depth, 2.97 ppt (30 % of truth) at 4 px.
↑ Exoplanet transit from aperture photometry — depth and duration fail separately ―― A synthetic star field (240 frames) carries a 10 ppt limb-darkened transit on the target only, with transparency variation, sub-pixel drift, flat-field non-uniformity and photon noise injected from separate random streams; fullseye’s star_detect → frame_align → aperture_photometry extracts the light curve. The zero point (target aperture sum alone) breaks under clouds to a depth error of +72 ppt; the ratio to comparison stars gives -0.13 ppt / T14 -0.9 fr. Comparison-star choice moves the residual rms from 1.91 to 11.43 ppt (6.0x), and inverse-variance weights computed from raw variance are fooled by clouds into 1.52x worse than a plain sum. The small-aperture cliff at 1σ was not the predicted centroid error but the op’s aperture-mask staircase (supersample=8: a static star sits 1.69x above theory, 0.93x at 32). The SNR=5 detection limit is 1.48 ppt measured vs 1.45 ppt theory with a known ephemeris, 2.0 ppt blind, where the duration breaks before the depth. Drift 2 px and a 3 % flat are harmless alone (0.16 / 0.08 ppt) but multiply into a 0.94 ppt false depth, 2.97 ppt (30 % of truth) at 4 px.
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_exoplanet_transit/02_frames_transit_phases.png)
↑ The measurement ―― 前/入/最深部/出/後の各段階で平均した画像からトランジット外の平均を引いた [e-]。4 倍拡大 (figure labels are in Japanese; the numbers are the same)
↑ ゼロ点と比較星の和は雲(全星共通)そのもの。
↑ この回の図
↑ この回の図
↑ この回の図
py -3.11 examples/poc_exoplanet_transit.py
Source: examples/poc_exoplanet_transit.py
This run produced 11 figures in total - see them all
Ops used (notes): aperture_photometry · frame_align · normalize · sigma_clip_stack · star_detect
| GNSS returns an ellipsoidal height; maps and drawings use an orthometric one, and the two differ by the geoid undulation — 30 to 40 m around Japan. This exhibit plants a synthetic geoid field and measures which quantities that confusion reaches and which ones it cancels out of. The floor is taken twice: the round trip of the existing operators closes to 1.07e-06 m, and an independent iterative implementation agrees to 7.2e-07 m in latitude — because a round trip alone cannot rule out both directions being wrong the same way. Several closed forms landed: the bound on slope error, atan | grad N | , predicted 0.008771 degrees against 0.008600 measured, with the constant-undulation control at 2.0e-14 degrees, which is exactly zero; the volume error A times the mean undulation agreed to 1.5e-08 cubic metres; the fraction of flat ground whose flow direction reverses was predicted at 76.1 % and measured at 76.2 %. One prediction was badly wrong: the bound on line-of-sight clearance was set at 2.60 m and measured 0.052 m, fifty times smaller, because the linear part of the undulation rides on the sight line and the ground equally and cancels; Earth curvature matters 276 times more. The flat-earth cliff has a closed form but not a single number — at 10 km the prediction of -7.8481 m brackets measurements of -7.8652 m north-south and -7.8303 m east-west, the difference between the meridional and prime-vertical radii. The theme is that the same 36 m is everything or nothing depending on the quantity: it nearly vanishes from differences and survives whole in absolutes, changing an earthwork volume by 3.97e+07 cubic metres and a flooded area from 41.9 % to zero. A control where the design surface is built from the same GNSS shows zero error — a contaminated ruler applied to a contaminated object hides the error. A datum mix-up shifts positions by 446.6 m on average while a relative check between baselines shows only 0.106 m per kilometre, four thousand times less; the danger is not the size but the plausibility. Counting quiet failures against loud ones over a 3600-point global grid, the only mistake that raises is swapping latitude and longitude, and only half the time; radians-for-degrees, a flipped longitude sign, swapped ECEF axes (whose results land in a valid latitude and longitude range 100 % of the time) and feet-for-metres all pass silently. Ten of eleven entries fail quietly. Along the way a real defect was found and fixed: near the Earth’s centre the ECEF-to-geodetic conversion returned a latitude of 180 degrees — a value that does not exist, and one its own inverse rejects. |
| *↑ Coordinates Go Wrong by Tens of Metres While Still Looking Plausible ―― GNSS returns an ellipsoidal height; maps and drawings use an orthometric one, and the two differ by the geoid undulation — 30 to 40 m around Japan. This exhibit plants a synthetic geoid field and measures which quantities that confusion reaches and which ones it cancels out of. The floor is taken twice: the round trip of the existing operators closes to 1.07e-06 m, and an independent iterative implementation agrees to 7.2e-07 m in latitude — because a round trip alone cannot rule out both directions being wrong the same way. Several closed forms landed: the bound on slope error, atan | grad N | , predicted 0.008771 degrees against 0.008600 measured, with the constant-undulation control at 2.0e-14 degrees, which is exactly zero; the volume error A times the mean undulation agreed to 1.5e-08 cubic metres; the fraction of flat ground whose flow direction reverses was predicted at 76.1 % and measured at 76.2 %. One prediction was badly wrong: the bound on line-of-sight clearance was set at 2.60 m and measured 0.052 m, fifty times smaller, because the linear part of the undulation rides on the sight line and the ground equally and cancels; Earth curvature matters 276 times more. The flat-earth cliff has a closed form but not a single number — at 10 km the prediction of -7.8481 m brackets measurements of -7.8652 m north-south and -7.8303 m east-west, the difference between the meridional and prime-vertical radii. The theme is that the same 36 m is everything or nothing depending on the quantity: it nearly vanishes from differences and survives whole in absolutes, changing an earthwork volume by 3.97e+07 cubic metres and a flooded area from 41.9 % to zero. A control where the design surface is built from the same GNSS shows zero error — a contaminated ruler applied to a contaminated object hides the error. A datum mix-up shifts positions by 446.6 m on average while a relative check between baselines shows only 0.106 m per kilometre, four thousand times less; the danger is not the size but the plausibility. Counting quiet failures against loud ones over a 3600-point global grid, the only mistake that raises is swapping latitude and longitude, and only half the time; radians-for-degrees, a flipped longitude sign, swapped ECEF axes (whose results land in a valid latitude and longitude range 100 % of the time) and feet-for-metres all pass silently. Ten of eleven entries fail quietly. Along the way a real defect was found and fixed: near the Earth’s centre the ECEF-to-geodetic conversion returned a latitude of 180 degrees — a value that does not exist, and one its own inverse rejects.* |
↑ The measurement ―― 分母 58081 セル。真値 24325(41.9 %)に対し、誤用は 0 と 58081。どちらの絵も「もっともらしい」(全面浸水は津波の図に見える)。 (figure labels are in Japanese; the numbers are the same)
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_geodetic_height_frames/03_flow_flip.png) |
| *↑ 真の勾配の中央値 9.15e-05 に対し | ∇N | は 1.2e-04。* |
↑ 2.5 cm/年(代表値)。
↑ 真の ECEF(dem_geodetic_to_ecef)を自前の ENU 回転に通して測った。
↑ 全球 3600 点。
py -3.11 examples/poc_geodetic_height_frames.py
Source: examples/poc_geodetic_height_frames.py
This run produced 13 figures in total - see them all
Ops used (notes): datum_tilt_check · dem_aspect · dem_datum_shift_3param · dem_earth_curvature_drop · dem_ecef_to_geodetic · dem_enu_from_geodetic · dem_flow_direction · dem_geodetic_from_enu · dem_geodetic_to_ecef · dem_geoid_height · dem_height_frame_convert · dem_slope · material_xray_attenuation · median · water_attenuation
A closed-form leaf outline with lesions of known area, over soil, side lighting, clipped highlights and a shadow, gives ground truth for disease severity (lesion pixels / leaf pixels). A fixed threshold on the green channel (the zero point) is off by +65.2 pt with soil alone; cutting the leaf with the illuminant-projected G and the lesions with Lab a* lands at -0.8 pt on the standard scene. Illumination falloff up to 50 % does not move a, but clipped highlights produce only false positives for a (+12.6 pt at 20 % coverage, 0.0 false negatives) versus +2.0 pt for hue, which is invariant to added white. With a 4 px soft lesion edge, choosing the 25 % or 75 % opacity contour as the boundary alone shifts the severity by ±3.5 pt (predicted within 0.4 pt by Steiner’s formula), and of 40 images placed within ±3 pt of a grade boundary, 19-35 are misgraded on soil by every method — on black cloth with a brightness leaf mask, a fixed hue threshold misgrades 8.
↑ Leaf disease severity — colour axes survive the lighting; the grade is decided by the leaf mask and the edge convention ―― A closed-form leaf outline with lesions of known area, over soil, side lighting, clipped highlights and a shadow, gives ground truth for disease severity (lesion pixels / leaf pixels). A fixed threshold on the green channel (the zero point) is off by +65.2 pt with soil alone; cutting the leaf with the illuminant-projected G and the lesions with Lab a lands at -0.8 pt on the standard scene. Illumination falloff up to 50 % does not move a, but clipped highlights produce only false positives for a (+12.6 pt at 20 % coverage, 0.0 false negatives) versus +2.0 pt for hue, which is invariant to added white. With a 4 px soft lesion edge, choosing the 25 % or 75 % opacity contour as the boundary alone shifts the severity by ±3.5 pt (predicted within 0.4 pt by Steiner’s formula), and of 40 images placed within ±3 pt of a grade boundary, 19-35 are misgraded on soil by every method — on black cloth with a brightness leaf mask, a fixed hue threshold misgrades 8.*
↑ The measurement ―― FN(青)の大きな塊は影と鏡面反射が重なった病斑(葉マスクごと落ちる)。FP(赤)は鏡面反射の下と病斑の縁。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ ゼロ点は固定しきい値を葉が割る列から予測できる。
↑ 色相は白を足しても動かない(定義)。
↑ 予測の崖 1.8 px。
py -3.11 examples/poc_leaf_disease_area.py
Source: examples/poc_leaf_disease_area.py
This run produced 9 figures in total - see them all
Ops used (notes): access_channel · blob_features · blob_label · blob_overlay · fill_holes · gaussian · linear_to_srgb · plant_color_index · reg_close · reg_erode · rgb_to_lab · select_largest · sk_otsu · specular_free_transform · srgb_to_linear · trans_from_rgb
Gaussian stars of known flux planted in the real background of the Hubble Deep Field (NASA/STScI, public domain) — the truth is certain because it was planted, and only the background is real. The real sky is not Gaussian: a robust spread of 1,474 e- against a plain standard deviation of 6,698 e- (4.54x), so taking std for noise reports a detection limit 4.5 times too generous. The background is not one number either, ranging 3,176 to 8,448 e- across 195 tiles of 64x64, which leaves up to 3.6 sigma of systematic error after a single global subtraction. Even in the emptiest thirty per cent of the field a fixed amount leaks into the aperture: recovery runs 4.38x at F = 2,000 e- and 1.058x at 100,000, and it is an addition rather than a factor — 1 + C/(F·frac) fits all five points to within 0.9 %, with C = 6,677 e-, a mere 3.0 % of background times effective area. Predicting first that the bias falls below 10 % from 67,521 e- and then measuring there gives 1.086 against the predicted 1.100. In crowded positions the order of magnitude changes, 341x down to 7.80x: a median is robust to outliers, but when half the field is contaminated the median is the contamination. The confidence lies in the same direction — the op reports an SNR of 19.2 where the closed form on the same background gives 4.2 — so gating on S/N passes the contaminated measurements first.
↑ Planting Known Stars in a Real Deep Field — Contamination Lies About the Measurement and the Confidence in the Same Direction ―― Gaussian stars of known flux planted in the real background of the Hubble Deep Field (NASA/STScI, public domain) — the truth is certain because it was planted, and only the background is real. The real sky is not Gaussian: a robust spread of 1,474 e- against a plain standard deviation of 6,698 e- (4.54x), so taking std for noise reports a detection limit 4.5 times too generous. The background is not one number either, ranging 3,176 to 8,448 e- across 195 tiles of 64x64, which leaves up to 3.6 sigma of systematic error after a single global subtraction. Even in the emptiest thirty per cent of the field a fixed amount leaks into the aperture: recovery runs 4.38x at F = 2,000 e- and 1.058x at 100,000, and it is an addition rather than a factor — 1 + C/(F·frac) fits all five points to within 0.9 %, with C = 6,677 e-, a mere 3.0 % of background times effective area. Predicting first that the bias falls below 10 % from 67,521 e- and then measuring there gives 1.086 against the predicted 1.100. In crowded positions the order of magnitude changes, 341x down to 7.80x: a median is robust to outliers, but when half the field is contaminated the median is the contamination. The confidence lies in the same direction — the op reports an SNR of 19.2 where the closed form on the same background gives 4.2 — so gating on S/N passes the contaminated measurements first.
↑ The measurement ―― 開口 1 つあたり C = 6677 e- の足し算として説明できる。 (figure labels are in Japanese; the numbers are the same)
↑ SNR は測ったフラックスから作るので、混入で分子が膨らむと S/N も膨らむ(F=2000 で 19.2 対 4.2)。
↑ 混雑側は桁が変わる。
py -3.11 examples/poc_real_sky_photometry.py
Source: examples/poc_real_sky_photometry.py
This run produced 4 figures in total - see them all
Ops used (notes): aperture_photometry
An 8 m-wide synthetic river with 1.5 m/s peak is orthorectified to estimate u_s(y). Q_proxy=integral h(y)u_s(y)dy is a surface-velocity proxy; actual Q=integral h(y)alpha(y)u_s(y)dy requires separately measured depth and alpha(y) (literature mean about 0.8, condition-dependent). Velocity RMS is 0.074 m/s and proxy error -7.8%; -2.5 points of the -3.0% control come from bank trapezoidal integration. Removing the search limit leaves the dt cliff at k=4, consistent here with pair loss and within-window variation. Noise sigma=0.03 alone is below 0.001 m/s.
↑ River Surface Velocity — Separating a Discharge Proxy from Actual Discharge ―― An 8 m-wide synthetic river with 1.5 m/s peak is orthorectified to estimate u_s(y). Q_proxy=integral h(y)u_s(y)dy is a surface-velocity proxy; actual Q=integral h(y)alpha(y)u_s(y)dy requires separately measured depth and alpha(y) (literature mean about 0.8, condition-dependent). Velocity RMS is 0.074 m/s and proxy error -7.8%; -2.5 points of the -3.0% control come from bank trapezoidal integration. Removing the search limit leaves the dt cliff at k=4, consistent here with pair loss and within-window variation. Noise sigma=0.03 alone is below 0.001 m/s.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ この回の図
↑ この回の図
↑ The animation ―― 動画(60 コマ、30 fps で撮った 2 秒を 1/3 の速さで再生): 左上 = 斜めカメラ(泡が右へ流れ、空の映り込みは動かない)、右上 = 既知ホモグラフィで正射化したコマと、いま足した対の PIV 変位(矢印 × 8)。下段は対を 1 つずつ足した平均から出した表面流速 u(y)(橙)と流量 Q の推移。1 対だけで Q = 11.68 m³/s(-7.3 %)、本文と同じ 20 対で 11.62 m³/s(-7.8 %、閉形式 12.6)、59 対で 11.63 m³/s(-7.7 %)—— 対を足しても流量の誤差はほとんど動かない。平均で減るのは偶然誤差だけで、u(y) が真値より低めに出る偏りと岸 0 の台形則(だけで約 -2.5 %)は残る。紫は斜め画像のまま 1 尺度で直したゼロ点(近岸で速く遠岸で遅い)
py -3.11 examples/poc_river_surface_velocity.py
Source: examples/poc_river_surface_velocity.py
This run produced 13 figures in total - see them all
Ops used (notes): arrow · axes_frame · axes_transform · highpass_image · median · piv_cross_correlate · piv_ensemble_correlate · piv_error_stats · piv_outlier_mask · piv_replace_outliers · piv_sample_at_windows · piv_to_velocity · plot_series · sigma_clip_stack · text_box · ticks · warp_by_plane
Ice concentration from a PSF (point spread function)-blurred two-band ice/water image, by hard classification and by linear unmixing. Hard classification is off by -4.2 points, unmixing by +0.02. The bias is explained by the perimeter fraction (R² = 0.984) and crosses zero near a concentration of 0.49 — validate only there and it passes.
↑ Sea-Ice Concentration — The Answer Depends on How Mixed Pixels Are Counted ―― Ice concentration from a PSF (point spread function)-blurred two-band ice/water image, by hard classification and by linear unmixing. Hard classification is off by -4.2 points, unmixing by +0.02. The bias is explained by the perimeter fraction (R² = 0.984) and crosses zero near a concentration of 0.49 — validate only there and it passes.
Terms: PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 下 3 段は端成分 5 % 誤差と薄氷 20 %(2 端成分の分解)。
py -3.11 examples/poc_sea_ice_concentration.py
Source: examples/poc_sea_ice_concentration.py
This run produced 4 figures in total - see them all
Ops used (notes): blob_features · blob_label · gauss_filter · mat_lstsq
Measure the lateral range curve p(x) from aerial imagery — detection probability as a function of cross-track distance — and hand its area, W = integral of p dx, to the search plan as the sweep width. This is where image processing and decision-making meet in a single number. The definition itself is demonstrated first: four curves of different shape (the measured p, a rectangle of width W, a triangle of base 2W, and a bimodal curve that cannot see nadir) all scaled to the same area of 256.2 m detect 0.2559 / 0.2563 / 0.2556 / 0.2566 of targets scattered uniformly over a half-width of 500 m, every one within 0.8 sigma of the predicted 0.2562. The shape vanishes; only the area survives. The cliff sits at coverage C = W v t / A = 1, printed in closed form before measuring: min(1, C) = 1.0000 and 1 - exp(-C) = 0.6321, against a rectangular control measuring 1.0000 and 0.6348 (the excess comes from having a finite 64 tracks; the exact value is 0.6350). Four predictions were wrong. Feeding the real p into a parallel sweep gives only 0.8464, not 1.000, because min(1, C) assumes the rectangular definite range law and a curve with tails overlaps its neighbouring tracks. The flatter curve was expected to be the more rectangle-like and therefore stronger, but it was the weaker (0.7705 against 0.8464): rectangle-like means standing inside W without tails, not being flat — while under random search the two curves tie, since random search only sees area. Ground resolution was expected to fall towards the swath edge, but for a nadir-looking central projection it is constant to 0.0e+00 relative spread; what falls is cos^4 vignetting, atmosphere and off-axis blur (an f-theta lens would coarsen the edge by 2.132 times). And subtracting a global background does nothing, because dividing by the whole-frame median and sigma is an affine map that cannot change the ranking (174.4 to 172.0 m); only a per-location subtraction helps (239.6 m). The optimum altitude lands in the interior at 220 m with W = 258.1 +/- 4.0 m, though 220 and 300 m are honestly not resolved apart; with an aircraft whose speed scales as min(1, h/600) the optimum moves to 420 m, so lowering the altitude to raise W does not work. The chaperone here is the false-alarm count, which concentrates at nadir rather than at the edge (113 in the 0-32 m band, zero in the outermost) because targets and whitecaps dim through the same cos^4: the best-seen place barks the most. Moving only the threshold slides W from 406 m to 170 m, so a sweep width quoted without a false-alarm rate says nothing at all.
↑ Sweep Width — One Number Measured from Aerial Images Decides Whether the Search Works ―― Measure the lateral range curve p(x) from aerial imagery — detection probability as a function of cross-track distance — and hand its area, W = integral of p dx, to the search plan as the sweep width. This is where image processing and decision-making meet in a single number. The definition itself is demonstrated first: four curves of different shape (the measured p, a rectangle of width W, a triangle of base 2W, and a bimodal curve that cannot see nadir) all scaled to the same area of 256.2 m detect 0.2559 / 0.2563 / 0.2556 / 0.2566 of targets scattered uniformly over a half-width of 500 m, every one within 0.8 sigma of the predicted 0.2562. The shape vanishes; only the area survives. The cliff sits at coverage C = W v t / A = 1, printed in closed form before measuring: min(1, C) = 1.0000 and 1 - exp(-C) = 0.6321, against a rectangular control measuring 1.0000 and 0.6348 (the excess comes from having a finite 64 tracks; the exact value is 0.6350). Four predictions were wrong. Feeding the real p into a parallel sweep gives only 0.8464, not 1.000, because min(1, C) assumes the rectangular definite range law and a curve with tails overlaps its neighbouring tracks. The flatter curve was expected to be the more rectangle-like and therefore stronger, but it was the weaker (0.7705 against 0.8464): rectangle-like means standing inside W without tails, not being flat — while under random search the two curves tie, since random search only sees area. Ground resolution was expected to fall towards the swath edge, but for a nadir-looking central projection it is constant to 0.0e+00 relative spread; what falls is cos^4 vignetting, atmosphere and off-axis blur (an f-theta lens would coarsen the edge by 2.132 times). And subtracting a global background does nothing, because dividing by the whole-frame median and sigma is an affine map that cannot change the ranking (174.4 to 172.0 m); only a per-location subtraction helps (239.6 m). The optimum altitude lands in the interior at 220 m with W = 258.1 +/- 4.0 m, though 220 and 300 m are honestly not resolved apart; with an aircraft whose speed scales as min(1, h/600) the optimum moves to 420 m, so lowering the altitude to raise W does not work. The chaperone here is the false-alarm count, which concentrates at nadir rather than at the edge (113 in the 0-32 m band, zero in the outermost) because targets and whitecaps dim through the same cos^4: the best-seen place barks the most. Moving only the threshold slides W from 406 m to 170 m, so a sweep width quoted without a false-alarm rate says nothing at all.
↑ The measurement ―― 半幅 500 m に一様に撒いた 400000 個。予測 W/2X = 0.2562、モンテカルロの標準偏差 0.0007。 (figure labels are in Japanese; the numbers are the same)
↑ 4 本とも面積 = W = 256 m。
↑ 完全平行は C=1 でちょうど 0 に落ちるが、実測の横距離曲線では 0.154 残る(裾が隣の航跡と重なるため)。
↑ だから走査幅は必ず誤検出率と対で報告する。
↑ 3 本とも誤検出 1.0 件/枚。
py -3.11 examples/poc_search_sweep_width.py
Source: examples/poc_search_sweep_width.py
This run produced 12 figures in total - see them all
Ops used (notes): gauss_filter · gray_tophat · integrate_funct_1d · laplace · ncc_locate · noise_sigma · photon_sample · relative_illumination · star_detect · tophat · vignette · xsk_blob_log
A limb-darkened solar disc seen through seeing, its radius measured by the 50 % rule, by gradient maximum and by model fitting. The prediction ‘bias scales with the darkening coefficient’ failed: at u = 0.8 the bias is -12.76 px (pure geometry predicts -13.14 px). The cancellation point near threshold 0.26, where blur appears to have no effect, moves to 0.38 when u changes.
↑ Where Is the Edge of a Limb-Darkened Disc? The 50 % Rule Reads the Radius Small ―― A limb-darkened solar disc seen through seeing, its radius measured by the 50 % rule, by gradient maximum and by model fitting. The prediction ‘bias scales with the darkening coefficient’ failed: at u = 0.8 the bias is -12.76 px (pure geometry predicts -13.14 px). The cancellation point near threshold 0.26, where blur appears to have no effect, moves to 0.38 when u changes.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 縁に載った黒点だけが効く(内側の黒点は縁の点列に入らない)。
py -3.11 examples/poc_solar_limb_darkening.py
Source: examples/poc_solar_limb_darkening.py
This run produced 4 figures in total - see them all
Ops used (notes): edge_points · gauss_filter · gaussian · gen_measure_rectangle2 · mat_lstsq · measure_pos
Stars rendered from known celestial coordinates, located by four methods and compared with the Fisher-information bound. At S/N 298 the centroid (null) sits at 5.56x the bound and the background-subtracted centroid at 1.03x; no method beats the bound. At the faint end the null appears to beat it (0.2790 px versus 0.3471 px), but with a sensitivity of 0.038 it is merely returning the rounded initial guess.
↑ To What Fraction of a Pixel Can a Star Be Located, and Where Is the Cliff? ―― Stars rendered from known celestial coordinates, located by four methods and compared with the Fisher-information bound. At S/N 298 the centroid (null) sits at 5.56x the bound and the background-subtracted centroid at 1.03x; no method beats the bound. At the faint end the null appears to beat it (0.2790 px versus 0.3471 px), but with a sensitivity of 0.038 it is merely returning the rounded initial guess.
↑ The measurement ―― 暗い端で素の重心が下限を割って見えるのは「動かない推定器」だから(感度 0.038)。 (figure labels are in Japanese; the numbers are the same)
↑ 素の重心だけ FWHM に依らない(空の希釈)。
↑ 混ぜた中央値は孤立星とも二重星とも違う「どこでもない値」。
py -3.11 examples/poc_star_astrometry.py
Source: examples/poc_star_astrometry.py
This run produced 4 figures in total - see them all
Ops used (notes): gaussian · noise_sigma · psf_fit · star_detect · vector_to_similarity
A 36-ring disc is synthesised in closed form with an off-centre pith, eccentric growth, circumferential wobble, wandering cracks, decay spots, grain texture and blur. A single-ray peak count (baseline) gets the ring count right in only 18 of 24 directions, yet the 6 wrong directions still give a width-series correlation of median 0.900. The consensus method (pith-centred polar unwrap, radius normalised by the disc edge, theta-median, 24 sector measure lines, median) returns exactly 36 rings, 0 missing, width correlation 0.996 (mean error 0.13 px). A 20 px pith error leaves the width correlation at 0.994: the cosine modulation lands on the radii (slope -14.9 px), not on the widths (-0.02 px); what it removes is the innermost rings (predicted 2 / measured 2). The thinnest ring is lost at 2.5 px (consensus) and 3.0 px (baseline), and at blur sigma 4 px the baseline counts 13 false rings.
↑ Counting tree rings and extracting the width series — ring count and width correlation fail separately ―― A 36-ring disc is synthesised in closed form with an off-centre pith, eccentric growth, circumferential wobble, wandering cracks, decay spots, grain texture and blur. A single-ray peak count (baseline) gets the ring count right in only 18 of 24 directions, yet the 6 wrong directions still give a width-series correlation of median 0.900. The consensus method (pith-centred polar unwrap, radius normalised by the disc edge, theta-median, 24 sector measure lines, median) returns exactly 36 rings, 0 missing, width correlation 0.996 (mean error 0.13 px). A 20 px pith error leaves the width correlation at 0.994: the cosine modulation lands on the radii (slope -14.9 px), not on the widths (-0.02 px); what it removes is the innermost rings (predicted 2 / measured 2). The thinnest ring is lost at 2.5 px (consensus) and 3.0 px (baseline), and at blur sigma 4 px the baseline counts 13 false rings.
↑ The measurement ―― 偏心成長で境界が θ とともに斜めに走るので、正規化しないとθ 窓の中で外側の年輪がにじむ。 (figure labels are in Japanese; the numbers are the same)
↑ 展開図(横 = 半径 px、縦 = 角度)に、扇形ごとの測定線が拾った境界(赤、下)と真値(青、上)を重ねた。
↑ ゼロ点は θ=0 方向の局所幅なので偏心成長ぶん尺度がずれる。
↑ 幅系列の相関はほぼ動かない。
↑ この回の図
py -3.11 examples/poc_tree_ring_dendro.py
Source: examples/poc_tree_ring_dendro.py
This run produced 9 figures in total - see them all
Ops used (notes): derivate_funct_1d · find_peaks · gen_measure_rectangle2 · measure_pos · median_rect · polar_unwrap · smooth_funct_1d_gauss
Cover fraction from a four-band field image, scored against the per-pixel leaf area fraction. The null (Otsu on the green channel) overshoots by +16.3 pp at mid-season, and since every method scatters by under 0.5 pp, nearly all of the difference is bias. At emergence on wet soil the null’s cover bias is -0.2 pp while precision and recall are both 0.000 — the number is right and not a single pixel is.
↑ Counting Crop Green — Ground Truth as Per-Pixel Leaf Area Fraction ―― Cover fraction from a four-band field image, scored against the per-pixel leaf area fraction. The null (Otsu on the green channel) overshoots by +16.3 pp at mid-season, and since every method scatters by under 0.5 pp, nearly all of the difference is bias. At emergence on wet soil the null’s cover bias is -0.2 pp while precision and recall are both 0.000 — the number is right and not a single pixel is.
Terms: Otsu — A histogram thresholding method that chooses the threshold maximizing the variance between the two resulting classes.
↑ The measurement ―― 影ゼロならゼロ点も悪くない。影は『暗さ』を手掛かりにする手法に直接刺さる。 (figure labels are in Japanese; the numbers are the same)
↑ 純粋な葉が 67 % ある場面。
↑ 白い画素の数だけが釣り合っている。
py -3.11 examples/poc_vegetation_cover.py
Source: examples/poc_vegetation_cover.py
This run produced 4 figures in total - see them all
Ops used (notes): cv_otsu · otsu · sk_otsu
The waterline detected on an obliquely photographed staff gauge and converted to stage. Linear conversion from two gauge marks bends off by up to -6.4 cm (at 1.00 m), and the sign is decided not by the stage but by whether the reading interpolates (-6.6 cm) or extrapolates (+16.3 cm). A four-point homography brings it under 0.2 cm; what remains is waterline detection, not perspective.
↑ River Stage From an Oblique Photo — Ignoring Perspective Bends the Row-Number Error Into an Arc ―― The waterline detected on an obliquely photographed staff gauge and converted to stage. Linear conversion from two gauge marks bends off by up to -6.4 cm (at 1.00 m), and the sign is decided not by the stage but by whether the reading interpolates (-6.6 cm) or extrapolates (+16.3 cm). A four-point homography brings it under 0.2 cm; what remains is waterline detection, not perspective.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 負 = 低く読む。
py -3.11 examples/poc_water_level.py
Source: examples/poc_water_level.py
This run produced 4 figures in total - see them all
Ops used (notes): gen_measure_rectangle2 · mat_svd · measure_pos · projective_trans_image · ransac_line · threshold
On soft sand a wheel turns without the body advancing by the same amount (slip ratio s), and on steeper slopes the rover gets stuck. The existing terrain.traversability is purely geometric and has no notion of soil that cannot hold. With learning-free soil mechanics and light statistics: Bekker pressure–sinkage → numerical integration of Wong–Reece stresses on a rigid wheel → steady slip per slope angle → actual slip from a ground-facing camera’s translation and the wheel rotation → slip uncertainty (quantile regression with conformal calibration, and a Gaussian process) → a cost map of CVaR-discounted edge travel time and 8-neighbour Dijkstra. Closed form: traction at τ = 0 equals −Bekker’s compaction resistance to 1.9e-7. Dry sand (Mars gravity, 50 kg, 4 wheels) slips 0.219 at 20° and climbs at most 25.2°; the rigid-ground limit is 26.35° (a MuJoCo rigid wheel gives 25.83°, and does not match on sand). Visual-odometry slip error at most 0.0034. Coverage of the nominal 90 % band: quantile regression 0.915; the Gaussian process covers only 0.780 above 8°, is too wide on gentle slopes (0.992), and outside the data answers ‘slip 0.29’ at 32° (truth: stuck). On a HiRISE DTM (central peak of Balvicar crater, public domain, outside the repo via FULLSEYE_ROVERSLIP_DATA) the mean-shortest path is 672 m and the minimum-risk path 696 m, mean time +1.1 %, probability of getting stuck under true slip 2.23 % → 0.05 %. Traps: Bekker’s textbook sinkage factor (3 − n)/3 is 17 % too deep at n = 1.9; the 0.4.0 phase_correlation_fft (no window, full whitening) returned (1, 0) on a non-periodic crop of the ground (truth (−2, −5); its default was fixed on 2026-10-06). Honestly: the slip truth is synthetic, the soil type is unknown, and with one soil the two paths nearly coincide. 18 gates (15 on CI without data or mujoco, 3.1 s, curves at 5° and training data as observed); the figure run and –full use 1° curves and training data through visual odometry.
↑ Predicting Wheel Slip with Uncertainty on Real Martian Terrain and Planning a Path that Avoids the Slip Risk — Bekker / Wong–Reece and CVaR on a HiRISE DTM; outside the Data a Gaussian Process Calls a Stuck Slope ‘Slip 0.29’ ―― On soft sand a wheel turns without the body advancing by the same amount (slip ratio s), and on steeper slopes the rover gets stuck. The existing terrain.traversability is purely geometric and has no notion of soil that cannot hold. With learning-free soil mechanics and light statistics: Bekker pressure–sinkage → numerical integration of Wong–Reece stresses on a rigid wheel → steady slip per slope angle → actual slip from a ground-facing camera’s translation and the wheel rotation → slip uncertainty (quantile regression with conformal calibration, and a Gaussian process) → a cost map of CVaR-discounted edge travel time and 8-neighbour Dijkstra. Closed form: traction at τ = 0 equals −Bekker’s compaction resistance to 1.9e-7. Dry sand (Mars gravity, 50 kg, 4 wheels) slips 0.219 at 20° and climbs at most 25.2°; the rigid-ground limit is 26.35° (a MuJoCo rigid wheel gives 25.83°, and does not match on sand). Visual-odometry slip error at most 0.0034. Coverage of the nominal 90 % band: quantile regression 0.915; the Gaussian process covers only 0.780 above 8°, is too wide on gentle slopes (0.992), and outside the data answers ‘slip 0.29’ at 32° (truth: stuck). On a HiRISE DTM (central peak of Balvicar crater, public domain, outside the repo via FULLSEYE_ROVERSLIP_DATA) the mean-shortest path is 672 m and the minimum-risk path 696 m, mean time +1.1 %, probability of getting stuck under true slip 2.23 % → 0.05 %. Traps: Bekker’s textbook sinkage factor (3 − n)/3 is 17 % too deep at n = 1.9; the 0.4.0 phase_correlation_fft (no window, full whitening) returned (1, 0) on a non-periodic crop of the ground (truth (−2, −5); its default was fixed on 2026-10-06). Honestly: the slip truth is synthetic, the soil type is unknown, and with one soil the two paths nearly coincide. 18 gates (15 on CI without data or mujoco, 3.1 s, curves at 5° and training data as observed); the figure run and –full use 1° curves and training data through visual odometry.
↑ The measurement ―― real Mars terrain at 1:1 pixels: the mean-slip shortest path and the minimum slip-risk path (figure labels are in Japanese; the numbers are the same)
↑ the risk-aware path trims the peaks of the upper band at almost no extra length
↑ GP’s uniform noise is too wide on gentle slopes, too narrow on steep ones, and reverts to the mean past the data
↑ the slope a wheel can hold is where DP/W reaches sin(slope)
↑ no sinkage, no shear deformation: a rigid-contact simulator cannot show soft-soil slip
↑ The animation ―― as the rover drives, measured slips tighten the slip-vs-slope band (quantile regression, 90 %)
py -3.11 examples/poc_rover_slip_risk_path.py
Source: examples/poc_rover_slip_risk_path.py
This run produced 7 figures in total - see them all
Ops used (notes): bekker_wheel_sinkage · cvar_cost_map · fbm_height · fbm_params · ground_shift_track · odometry_slip · path_slip_risk · risk_aware_path · slip_gp_fit · slip_predict · slip_quantile_fit · slope_slip_curve · text_box · wheel_forces · wheel_sinkage · wheel_traction_curve
| The synthetic companion (poc_geodetic_height_frames) showed that mixing ellipsoidal and orthometric heights costs tens of metres, using numbers we made ourselves. This exhibit checks the same claim against numbers somebody else measured and published. An NGS datasheet gives, for one mark, the ellipsoidal height h (NAD 83(2011)), the orthometric height H (NAVD 88), the geoid height N (GEOID18) and the Cartesian coordinates (x,y,z) — all four — so 523 marks were taken from the Colorado Front Range, where the geoid is steep enough for interpolation error to show. An existing operator was finally checked against the outside world: dem_geodetic_to_ecef lands within 0.5 mm rms (0.8 mm worst) of the published Cartesian coordinates, and the inverse closes to 7.1e-09 degrees, inside the 9.0e-09-degree floor set by the millimetre rounding of the published data — until now this operator had only been checked against its own inverse, which tests consistency, not correctness. Interpolating a coarse grid costs more than a generation of geoid model: bilinear interpolation of a 0.25-degree GEOID18 grid differs from the published point values by 11.1 cm rms (44.5 cm worst), while GEOID18 minus GEOID12B is -1.0 cm on average (range -9.8 to +6.3 cm). Fifteen of the 523 marks fall outside the grid and are refused rather than clamped, because an extrapolated undulation is not a survey value. A height-frame mix-up does not look like an outlier: putting h into the orthometric column places every single mark exactly on the line -N, lifting the whole table by 16.75 m. And the residual sorts the marks by how they were surveyed even though it never reads that metadata: median | h-H-N | is 1.6 cm for levelling, 1.9 cm for network adjustment, 3.8 cm for GPS observation and 8.3 cm (1.02 m worst) for VERTCON3, a modelled datum conversion. Even the published triples do not close exactly (10.6 cm rms): NAVD 88 comes from levelling and GEOID18 from gravity, and the mismatch shows up here. Only a 56 KB aggregate ships; the raw download stays out of the repository. |
| *↑ There Are Two Kinds of Height, for Real — Putting a Height-Frame Mix-Up Under a Detector with 523 Published Survey Marks ―― The synthetic companion (poc_geodetic_height_frames) showed that mixing ellipsoidal and orthometric heights costs tens of metres, using numbers we made ourselves. This exhibit checks the same claim against numbers somebody else measured and published. An NGS datasheet gives, for one mark, the ellipsoidal height h (NAD 83(2011)), the orthometric height H (NAVD 88), the geoid height N (GEOID18) and the Cartesian coordinates (x,y,z) — all four — so 523 marks were taken from the Colorado Front Range, where the geoid is steep enough for interpolation error to show. An existing operator was finally checked against the outside world: dem_geodetic_to_ecef lands within 0.5 mm rms (0.8 mm worst) of the published Cartesian coordinates, and the inverse closes to 7.1e-09 degrees, inside the 9.0e-09-degree floor set by the millimetre rounding of the published data — until now this operator had only been checked against its own inverse, which tests consistency, not correctness. Interpolating a coarse grid costs more than a generation of geoid model: bilinear interpolation of a 0.25-degree GEOID18 grid differs from the published point values by 11.1 cm rms (44.5 cm worst), while GEOID18 minus GEOID12B is -1.0 cm on average (range -9.8 to +6.3 cm). Fifteen of the 523 marks fall outside the grid and are refused rather than clamped, because an extrapolated undulation is not a survey value. A height-frame mix-up does not look like an outlier: putting h into the orthometric column places every single mark exactly on the line -N, lifting the whole table by 16.75 m. And the residual sorts the marks by how they were surveyed even though it never reads that metadata: median | h-H-N | is 1.6 cm for levelling, 1.9 cm for network adjustment, 3.8 cm for GPS observation and 8.3 cm (1.02 m worst) for VERTCON3, a modelled datum conversion. Even the published triples do not close exactly (10.6 cm rms): NAVD 88 comes from levelling and GEOID18 from gravity, and the mismatch shows up here. Only a 56 KB aggregate ships; the raw download stays out of the repository.* |
↑ The measurement ―― 523 NGS marks, each publishing ellipsoidal height h (NAD 83), orthometric height H (NAVD 88) and geoid height N (GEOID18). The residual h - H - N is not exactly zero: NAVD 88 and GEOID18 were built from different measurements, and the mismatch shows up here at the centimetre level (rms 0.106 m, median -0.004 m). 74 of 523 marks exceed 0.1 m (figure labels are in Japanese; the numbers are the same)
↑ dem_height_frame_residual reads only the three height columns — never the metadata saying how each mark was measured.
↑ the published GEOID18 undulation on a 0.25-degree grid over the Colorado Front Range (-19.40 to -11.15 m, 9x7 nodes).
↑ GEOID18 minus GEOID12B on the same nodes: mean -0.010 m, range -0.098 to +0.063 m.
↑ 523 marks placed in the east-north-up frame of one of them (AB3303).
py -3.11 examples/poc_geodetic_benchmarks_real.py
Source: examples/poc_geodetic_benchmarks_real.py
This run produced 9 figures in total - see them all
Ops used (notes): dem_datum_shift_3param · dem_ecef_to_geodetic · dem_enu_from_geodetic · dem_geodetic_from_enu · dem_geodetic_to_ecef · dem_geoid_height · dem_height_frame_convert · dem_height_frame_residual
| Gravitational lensing carries exact invariants that can be measured straight off the picture, and the tools that measure them are ordinary 2-D operators already in the box: connected components, area and centroid. Writing a point-mass lens in units of the Einstein radius, a source at position u produces images at theta = (u +/- sqrt(u^2+4))/2 with magnifications mu = (u^2+2)/(2 u sqrt(u^2+4)) +/- 1/2, from which two equalities hold no matter where the source sits: the product of the image positions is exactly -1, and the difference of the magnifications is exactly 1, an integer. Across nine source positions the first moves by 8.9e-16 and the second by 2.2e-16. The first core finding is that the integer comes out of the picture: rendering by tracing image-plane pixels back to the source plane, splitting the result into two images with blob_label and subtracting their areas gives a deviation from 1 of 0.0660, 0.0216 and 0.0126 on grids of 801, 1601 and 3201 - measured from the drawing, not read off the model. The second is that surface brightness is exactly conserved, as Liouville’s theorem requires: the interior value of the images stays at 1.000000000000000 while the area falls from 2,322 to 480 pixels, so one frame yields both a quantity that does not change and one that does. The third is that the width of the Einstein ring equals the radius of the source, because the radial magnification on the ring is exactly one half; across four source radii the largest discrepancy is 7.2e-05. The fourth is that the singularity manufactures a one-pixel false image: with the source exactly behind the lens the image should be a single ring, yet the connected components number two, the extra one being the pixel at the lens centre where the deflection diverges - a gate that counts images lies here. Two predictions were wrong and have been left in. The first was that measuring from the picture would struggle where the faint image shrinks, at u = 1.5; in fact the worst case at every resolution was u = 0.3, near the caustic, and the faint image at u = 1.5 still covers 209 pixels - the difficulty is not small images but images stretched into thin arcs. The second was the anti-aliasing itself: the edge coverage was being computed from a distance in the source plane while the pixel width belongs to the image plane, and dividing the distance field by its image-plane gradient dropped the u = 0.3 deviation from 0.0214 to 0.0126. For a singular isothermal sphere the image separation is independent of source position at twice the Einstein radius, with a largest discrepancy of 9.3e-03 over four positions. In the animation of a source crossing behind the lens, the measured magnification tracks the closed form to a relative difference of 0.009 while | u | >= 0.3 and opens to 0.161 closer to the caustic, so the range over which the method works is reported alongside the claim. The figures use a calibration target as the source - four concentric rings and twelve radial spokes - so the shear can be read by eye. No new operator was added. |
| *↑ Scoring Gravitational-Lens Images with Ordinary Industrial Measurement Operators ―― Gravitational lensing carries exact invariants that can be measured straight off the picture, and the tools that measure them are ordinary 2-D operators already in the box: connected components, area and centroid. Writing a point-mass lens in units of the Einstein radius, a source at position u produces images at theta = (u +/- sqrt(u^2+4))/2 with magnifications mu = (u^2+2)/(2 u sqrt(u^2+4)) +/- 1/2, from which two equalities hold no matter where the source sits: the product of the image positions is exactly -1, and the difference of the magnifications is exactly 1, an integer. Across nine source positions the first moves by 8.9e-16 and the second by 2.2e-16. The first core finding is that the integer comes out of the picture: rendering by tracing image-plane pixels back to the source plane, splitting the result into two images with blob_label and subtracting their areas gives a deviation from 1 of 0.0660, 0.0216 and 0.0126 on grids of 801, 1601 and 3201 - measured from the drawing, not read off the model. The second is that surface brightness is exactly conserved, as Liouville’s theorem requires: the interior value of the images stays at 1.000000000000000 while the area falls from 2,322 to 480 pixels, so one frame yields both a quantity that does not change and one that does. The third is that the width of the Einstein ring equals the radius of the source, because the radial magnification on the ring is exactly one half; across four source radii the largest discrepancy is 7.2e-05. The fourth is that the singularity manufactures a one-pixel false image: with the source exactly behind the lens the image should be a single ring, yet the connected components number two, the extra one being the pixel at the lens centre where the deflection diverges - a gate that counts images lies here. Two predictions were wrong and have been left in. The first was that measuring from the picture would struggle where the faint image shrinks, at u = 1.5; in fact the worst case at every resolution was u = 0.3, near the caustic, and the faint image at u = 1.5 still covers 209 pixels - the difficulty is not small images but images stretched into thin arcs. The second was the anti-aliasing itself: the edge coverage was being computed from a distance in the source plane while the pixel width belongs to the image plane, and dividing the distance field by its image-plane gradient dropped the u = 0.3 deviation from 0.0214 to 0.0126. For a singular isothermal sphere the image separation is independent of source position at twice the Einstein radius, with a largest discrepancy of 9.3e-03 over four positions. In the animation of a source crossing behind the lens, the measured magnification tracks the closed form to a relative difference of 0.009 while | u | >= 0.3 and opens to 0.161 closer to the caustic, so the range over which the method works is reported alongside the claim. The figures use a calibration target as the source - four concentric rings and twelve radial spokes - so the shear can be read by eye. No new operator was added.* |
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_gravitational_lens_invariants/02_disc_images_vs_u.png) |
| *↑ The measurement ―― 面積を測るのに模様は要らない。明るいほうの像は外側(θ₊ > 1)、暗いほうは内側( | θ₋ | < 1)に出て、離れるほど内側の像は小さく暗くなる。 (figure labels are in Japanese; the numbers are the same)* |
↑ 9 通りの u で θ₊·θ₋ は −1 から 8.9e-16、μ₊ − μ₋ は 1 から 2.2e-16 しか動かない。
↑ 像の内部の値はどちらも厳密に 1.000000000000000。
↑ 環の上では動径方向の倍率が厳密に 1/2 なので、直径 2ρ の光源は太さ ρ の環になる。
↑ 重心は blob_label で分けた成分ごとに、被覆率で重みを付けて出している。
↑ The animation ―― 光源がレンズの裏を横切る(図は校正ターゲット)。最接近で 2 つの像が伸びて環に近づく。倍率の数字は同じ光源位置を無地の円盤で描き直して測ったもので、最大 19.61 倍。
py -3.11 examples/poc_gravitational_lens_invariants.py
Source: examples/poc_gravitational_lens_invariants.py
This run produced 12 figures in total - see them all
Ops used (notes): blob_label
UAV paddy RGB is divided into 16 px blocks. A supervised ten-feature Fisher LDA is fitted on train, the grid candidate selected on validation, and 148 test images scored once. Predeclared edges are 5/25/50%. Agreement is 0.574 (majority 0.345), kappa 0.424, cover correlation 0.826 and IoU 0.397 (random 0.189). Swapping R/G gives kappa -0.023. Holding out flight dates, one different-season date has within-date correlation 0.14; the bundled split measures other frames from seen flights.
↑ Weedy-Rice Cover — Supervised Linear Discrimination without Deep Learning ―― UAV paddy RGB is divided into 16 px blocks. A supervised ten-feature Fisher LDA is fitted on train, the grid candidate selected on validation, and 148 test images scored once. Predeclared edges are 5/25/50%. Agreement is 0.574 (majority 0.345), kappa 0.424, cover correlation 0.826 and IoU 0.397 (random 0.189). Swapping R/G gives kappa -0.023. Holding out flight dates, one different-season date has within-date correlation 0.14; the bundled split measures other frames from seen flights.
↑ The measurement ―― 作物は細く濃い葉が畝の向きに揃い、雑草は広く明るい葉がばらばらの向き。雑草の割合は乱数場の分位点で厳密に置いた。判定は 16 px 角の区画ごとの Fisher 判別。 (figure labels are in Japanese; the numbers are the same)
↑ 葉の形と配置は同じ乱数で同じ。
↑ データ付属の分割は同じ飛行の隣り合うコマを train と test に分ける(コマは重なる)。
↑ 対角が一致。
↑ 正の重みは雑草イネの側。
py -3.11 examples/poc_weed_rice_ratio.py
Source: examples/poc_weed_rice_ratio.py
This run produced 10 figures in total - see them all
Ops used (notes): cover_ratio_grade · fisher_lda_fit · fisher_lda_score · msa_attribute_agreement · plant_color_index · snic_superpixels · superpixel_color_texture_features
Deblurring, upscaling, dehazing, focus stacking, reconstructing from projections, ranging by counting photons. Restoration is where ‘it looks better’ and ‘it is closer to the truth’ are most easily confused. The 20 exhibits here synthesise the kernel, the depth, the airlight, the PSD, the projections and the arrival time themselves, so the two can be scored separately.
Appearance metrics do not peak at the truth: a hazy input has higher contrast than the true scene; unsharp masking matches the true gradient energy while PSNR drops; adding noise raises PSNR. Conversely, a method can restore stripes finer than Nyquist while PSNR moves by only -0.01 dB.
The null baseline placed throughout is ‘do nothing’. Deblurring with the kernel angle off by 19.4 degrees, FBP from 12 projections, dehazing at visibility above 782 m, depth in textureless regions: each loses to that baseline. Stating the losing conditions in numbers is what this room is for.
Deblurring depends on estimating motion direction and length correctly. Known linear motion was applied, and restoration was scored against the original at 20 dB SNR. A wrong angle loses to no correction, while one kernel cannot undo rotational shake whose direction changes across the frame.
↑ How Much Camera Shake Can Be Undone — Make the Kernel, Apply It, Invert It, Compare ―― A known linear blur kernel applied and inverted, with the ceiling set by noise and by kernel estimation error. Without noise the image recovers from 22.38 to 56.41 dB; at 20 dB SNR the gain is 1.82 dB. A kernel angle off by 19.4 degrees is beaten by doing nothing, and undoing rotational blur with a single kernel makes the centre of rotation worse by -49.41 dB.
↑ The measurement ―― 4 枚目は「復元した」形をしているが、ゼロ点(観測そのもの)より悪い。絵の見た目では区別できない。 (figure labels are in Japanese; the numbers are the same)
↑ 交点が現場で効く数字。
↑ 核を作った右側だけが正。
↑ The animation ―― 15 px・20 度の直線ブレに SNR 40 dB の雑音を載せた観測を、角度を 0〜30 度ずらした核で Wiener 復元し直していく(正則化量は毎回神託で最良化)。ずれ 0 度で 28.87 dB、20 度で 22.31 dB。0.5 度刻みで追うと 19.2 度でアンシャープマスク(22.40 dB)に、19.4 度で「何もしない」(22.38 dB)に抜かれる(本文の表の補間では 19.2 / 19.4 度)。抜かれた後の復元も「復元した」形をしている —— 右の誤差地図でだけ、縞状のリンギングが真値からのずれとして見える。
py -3.11 examples/poc_camera_shake_deblur.py
Source: examples/poc_camera_shake_deblur.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · plot_series · psnr · reflect · ssim · text_box · ticks · unsharp · vol_richardson_lucy
A smooth field with provably zero steps was painted and the false edges counted in CIE L* and CIEDE2000 (perceptual colour-difference formula): jet raises 3, hsv 4, while viridis and gray raise none. Their positions follow in closed form from the sRGB transfer function and the CIE Y weights — jet’s lightness reversals are predicted at 0.3750 / 0.4490 / 0.6250 and measured at 0.3750 / 0.4492 / 0.6250 (max error 0.0002). A real step only out-shouts the false edges above 0.296 %FS with jet versus 0.050 %FS with viridis, matching a prediction made from the colormap LUT alone: jet demands a 5.9x taller step. The value-to-colour mapping matters more than the palette — over a 4.3-decade 1/r^2 field the effective number of distinguishable levels goes from 1.4 (linear) to 76.0 (rank) — and a single outlier costs log 41 % of them (27.4 to 16.3) while percentile and rank are untouched.
↑ Pseudo-colour changes what the reader decides — counting edges that are not there ―― A smooth field with provably zero steps was painted and the false edges counted in CIE L and CIEDE2000 (perceptual colour-difference formula): jet raises 3, hsv 4, while viridis and gray raise none. Their positions follow in closed form from the sRGB transfer function and the CIE Y weights — jet’s lightness reversals are predicted at 0.3750 / 0.4490 / 0.6250 and measured at 0.3750 / 0.4492 / 0.6250 (max error 0.0002). A real step only out-shouts the false edges above 0.296 %FS with jet versus 0.050 %FS with viridis, matching a prediction made from the colormap LUT alone: jet demands a 5.9x taller step. The value-to-colour mapping matters more than the palette — over a 4.3-decade 1/r^2 field the effective number of distinguishable levels goes from 1.4 (linear) to 76.0 (rank) — and a single outlier costs log 41 % of them (27.4 to 16.3) while percentile and rank are untouched.*
Terms: CIEDE2000 — A CIE colour-difference formula in CIELAB that corrects the lightness, chroma, and hue terms to better follow visual differences.
↑ The measurement ―― 平らなら偽の境目は立たない。jet と hsv の山がそのまま『見えてしまう帯』になる。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ jet の輪が偽の境目。
↑ 左は範囲外と『ちょうど端』が同じ色。
↑ 番号の大小に意味は無いのに、連続マップは『近い番号 = 近い領域』と読ませる。
py -3.11 examples/poc_colormap_readability.py
Source: examples/poc_colormap_readability.py
This run produced 14 figures in total - see them all
Ops used (notes): delta_e_map · percentile · rgb_to_lab
A 9×9 plenoptic synthesis of the ommatidial array, measuring the SNR gain when the same point is pooled over N ommatidia. For small apertures √N holds almost exactly (N=5: 2.25 vs 2.24), but for large ones the gain saturates (N=49: 5.33 vs 7.00). The cause is not the noise but the error in the outer 4 pixels of the shifted views: the noise-only gain is 7.05 (√N holds), and excluding an 8-pixel border gives 7.19. The fly’s six-fold pooling of R1–R6 sits below the knee. Depth comes from the array (near +1.998, far +0.499 against true 2.00 / 0.50), and a minority occluder is seen through by median pooling (RMS on hidden pixels: single view 0.237 → mean 0.065 → median 0.025).
↑ The Fly’s Compound Eye Is a Light-Field Sensor — Neural Superposition Pays Only Up to the Knee ―― A 9×9 plenoptic synthesis of the ommatidial array, measuring the SNR gain when the same point is pooled over N ommatidia. For small apertures √N holds almost exactly (N=5: 2.25 vs 2.24), but for large ones the gain saturates (N=49: 5.33 vs 7.00). The cause is not the noise but the error in the outer 4 pixels of the shifted views: the noise-only gain is 7.05 (√N holds), and excluding an 8-pixel border gives 7.19. The fly’s six-fold pooling of R1–R6 sits below the knee. Depth comes from the array (near +1.998, far +0.499 against true 2.00 / 0.50), and a minority occluder is seen through by median pooling (RMS on hidden pixels: single view 0.237 → mean 0.065 → median 0.025).
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
py -3.11 examples/poc_compound_eye.py
Source: examples/poc_compound_eye.py
This run produced 4 figures in total - see them all
Ops used (notes): lf_all_in_focus · lf_aperture_mask · lf_depth_from_focus · lf_plenoptic_design · lf_subaperture · lf_synthesize · lf_synthetic_aperture · median
A hazy image built from depth, airlight and extinction so that the true transmission and the true scene are both known, then dehazed and scored. The overall +4.04 dB hides a -1.49 dB degradation of the near field covered by +4.03 dB mid and +11.28 dB far. Above 782 m visibility dehazing does harm, and adding noise raises PSNR (an accidental cancellation).
↑ Removing Haze — Ground Truth From the Scattering Model, Transmission and Airlight Scored Apart ―― A hazy image built from depth, airlight and extinction so that the true transmission and the true scene are both known, then dehazed and scored. The overall +4.04 dB hides a -1.49 dB degradation of the near field covered by +4.03 dB mid and +11.28 dB far. Above 782 m visibility dehazing does harm, and adding noise raises PSNR (an accidental cancellation).
↑ The measurement ―― 空では過大評価(明るい側)、近景では過小評価(暗い側)。全体の平均バイアスでは打ち消し合って見えない。 (figure labels are in Japanese; the numbers are the same)
↑ 全体 +4.04 dB の正体。
↑ 左端(薄い霞 = 視程が長い側)で利得が 0 を割る。
↑ The animation ―― 動画(110 コマ、120 × 160 px を 2 倍で表示): 消散係数 beta を 0.0025(視程 1565 m)から 0.08(視程 49 m)まで連続に振る。上段は霞んだ観測・暗チャネル除霞・真値、下段は推定した透過率・真の透過率と、暗チャネル除霞の PSNR 利得(対 何もしない)の曲線。利得は beta ≦ 0.0065(視程 606 m 以上)で負 = 薄い霞では除霞が害になる(5 節の 7 点の表では 0.0050 と 0.0075 の間)。濃霧の端では +2.98 dB。真の A と t を与えたオラクルの PSNR は中央上の板に併記。表示は 8 bit に丸めた観測をそのまま使っている。
py -3.11 examples/poc_dehazing.py
Source: examples/poc_dehazing.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · clahe · equalize · image_entropy · joint_bilateral · nice_ticks · plot_series · psnr · rank_image · scene_radiance · ssim · text_box · ticks
| One eye assembled from a 721-ommatidium hexagonal lattice → acceptance function → lamina DC removal → Hassenstein-Reichardt correlators → HS opponent sum, turning under a 1/f banded sky. The direction and waveform of self-rotation are readable (correlation with the true yaw rate +0.785, +0.880 with a membrane low-pass, sign agreement 0.92), but skipping the lamina DC removal makes the correlator emit a DC × high-pass ripple and the correlation drops to +0.504. Walking forward without turning, the full-field readout turns into a −0.689 ‘rotation’; restricting to the upper field still leaks −0.221 because acceptance functions straddle the horizon (0.000 above el = 2Δρ). The opponent ratio discards speed, so a 1.4°/s flow from a dome 20 m away reads −0.612. The LGMD η peak comes α·l/ | v | before collision (−0.1570 s, predicted −0.1567 s) at θ = 23.97° (predicted 24.02°; 24.6° for the exact subtense of a sphere, a quartic root), the sphere τ rides d/ | v | with RMS 1e-5 s, and the disk formula misapplied to a sphere reads cos²(θ/2) = 0.75× at 60°. Twelve grating directions give DSI 0.798 with preferred direction 0°. |
| *↑ The Fly’s Visual Front End as a Chain of Operators — Turning and Walking Through a Synthetic Sky, What Can and Cannot Be Read ―― One eye assembled from a 721-ommatidium hexagonal lattice → acceptance function → lamina DC removal → Hassenstein-Reichardt correlators → HS opponent sum, turning under a 1/f banded sky. The direction and waveform of self-rotation are readable (correlation with the true yaw rate +0.785, +0.880 with a membrane low-pass, sign agreement 0.92), but skipping the lamina DC removal makes the correlator emit a DC × high-pass ripple and the correlation drops to +0.504. Walking forward without turning, the full-field readout turns into a −0.689 ‘rotation’; restricting to the upper field still leaks −0.221 because acceptance functions straddle the horizon (0.000 above el = 2Δρ). The opponent ratio discards speed, so a 1.4°/s flow from a dome 20 m away reads −0.612. The LGMD η peak comes α·l/ | v | before collision (−0.1570 s, predicted −0.1567 s) at θ = 23.97° (predicted 24.02°; 24.6° for the exact subtense of a sphere, a quartic root), the sphere τ rides d/ | v | with RMS 1e-5 s, and the disk formula misapplied to a sphere reads cos²(θ/2) = 0.75× at 60°. Twelve grating directions give DSI 0.798 with preferred direction 0°.* |
↑ The measurement ―― 相関 +0.785(膜 LP つき +0.880、ラミナ段なし +0.504)。対向比は符号の一致度なので、尺度 k は最小二乗で合わせてある。 (figure labels are in Japanese; the numbers are the same)
↑ 偏り: 全視野 -0.689 / 上半 el>0 -0.221 / el>2Δρ +0.000 / ドーム -0.612
↑ ピーク -0.157 s(予測 -0.157 s)、θ_peak 24.0°(予測 24.0°)。
↑ 球式は RMS 0.00001 s で真値に乗る。
↑ 負の応答(逆向き)は fly_dsi が 0 に切る。
py -3.11 examples/poc_fly_vision.py
Source: examples/poc_fly_vision.py
This run produced 6 figures in total - see them all
Ops used (notes): fly_dsi · fly_emd_response · fly_hex_lattice · fly_hex_resample · fly_hs_readout · fly_lgmd_eta · fly_sky_1f · fly_tau_from_expansion
An all-in-focus image and a depth map pulled from 17 frames blurred by the closed-form circle of confusion. The all-in-focus image reaches 33.69 dB (null = the middle frame, 28.52 dB), yet the depth from the same fusion loses to the null by 6x (0.16x) in textureless regions. A relative confidence scores 0.9575 there, higher than the 0.9381 of textured regions; rejecting by absolute value (1038x apart) brings the RMS from 0.918 to 0.817 mm.
↑ Focus Stacking — The All-in-Focus Image and the Depth Map Are Different Things ―― An all-in-focus image and a depth map pulled from 17 frames blurred by the closed-form circle of confusion. The all-in-focus image reaches 33.69 dB (null = the middle frame, 28.52 dB), yet the depth from the same fusion loses to the null by 6x (0.16x) in textureless regions. A relative confidence scores 0.9575 there, higher than the 0.9381 of textured regions; rejecting by absolute value (1038x apart) brings the RMS from 0.918 to 0.817 mm.
↑ The measurement ―― 合焦点法は全焦点画像と同時に距離画像も出す。ただし左下の無テクスチャの四角だけ、誤差が掃引全域にばらけた乱数になっている(段差帯のハローも見える)—— 絵ほど距離は当てにならない。 (figure labels are in Japanese; the numbers are the same)
↑ 左下の無テクスチャの四角が、相対量(0.90-1.00 に切って表示)では最も明るい = 自信ありに見え、絶対量(対数)でだけ「何も見えていない」と出る。
↑ 2 本が離れていく = 残りの誤差は標本化ではなく焦点評価が持っている。
↑ The animation ―― 焦点を 196.23〜203.00 mm で 17 枚掃引し、1 枚進むたびに「ここまでで焦点評価が最大のフレーム」を画素ごとに選び直す。左 = いまの 1 枚(橙 = この 1 枚で最良が更新された画素)、中 = ここまでの全焦点画像、右 = ここまでの距離画像。全焦点画像の PSNR は 22.17 dB から 33.69 dB へ育ち、中央の 1 枚(28.52 dB)を上回る。一方、左下の無地の四角は最後まで掃引のたびに塗り替わり(最後の 1 枚でも無地の 12 % が入れ替わる。テクスチャ有は 4 %)、距離誤差は 2.203 mm(テクスチャ有 0.467 mm)で終わる。
py -3.11 examples/poc_focus_stacking.py
Source: examples/poc_focus_stacking.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · csi_height_map · defocus_blur · depth_from_focus · dilation_circle · fuse · gauss_filter · laplace · mean_image · optical_camera · plot_series · psnr · sobel_amp · text_box · ticks · xcv2_lap_var
The truth here is a real photograph (scikit-image camera, CC0) and only the degradation is planted: a straight 11-pixel blur at 20 degrees plus noise of sigma 0.004. Doing nothing scores 24.04 dB, and a Wiener filter at the commonly used nsr of 0.005 scores 23.66 — it loses to the null. Stopping there would be misrepresenting the method: sweeping nsr gives 19.18 dB at 0.0005 and 25.40 dB at 0.02, so a fixed knob is not a comparison at all. The knob moves the result by 6.22 dB while deconvolving at all is worth 1.36 dB, a factor of 4.6 — the setting matters more than the method. Applying three rulers to the same seven results puts three different methods first: PSNR picks the correct-PSF Wiener at 25.40 dB, gradient energy picks motion_deblur at 0.2986, which is 1.54 times the truth’s own 0.1936 — an image sharper than the truth wins — and blur_effect picks unsharp. The methods those reference-free rulers choose cost 4.98 dB and 3.26 dB against the best. In fairness, blur_effect does rank the truth itself above every method: it measures blur correctly and still loses you three decibels when used to choose. Mistaking the blur length as 21 instead of 11 costs 5.48 dB against doing nothing while raising the gradient energy to 1.37 times the truth, so it looks like it worked. The cliffs: at sigma 0.016 the gain falls to 0.05 dB, and the gain over blur length peaks at 5 pixels, falling at both ends because a short blur has little to remove and a long one has destroyed the information.
↑ Deblurring a Real Photograph — Three Rulers, Three Different Winners ―― The truth here is a real photograph (scikit-image camera, CC0) and only the degradation is planted: a straight 11-pixel blur at 20 degrees plus noise of sigma 0.004. Doing nothing scores 24.04 dB, and a Wiener filter at the commonly used nsr of 0.005 scores 23.66 — it loses to the null. Stopping there would be misrepresenting the method: sweeping nsr gives 19.18 dB at 0.0005 and 25.40 dB at 0.02, so a fixed knob is not a comparison at all. The knob moves the result by 6.22 dB while deconvolving at all is worth 1.36 dB, a factor of 4.6 — the setting matters more than the method. Applying three rulers to the same seven results puts three different methods first: PSNR picks the correct-PSF Wiener at 25.40 dB, gradient energy picks motion_deblur at 0.2986, which is 1.54 times the truth’s own 0.1936 — an image sharper than the truth wins — and blur_effect picks unsharp. The methods those reference-free rulers choose cost 4.98 dB and 3.26 dB against the best. In fairness, blur_effect does rank the truth itself above every method: it measures blur correctly and still loses you three decibels when used to choose. Mistaking the blur length as 21 instead of 11 costs 5.48 dB against doing nothing while raising the gradient energy to 1.37 times the truth, so it looks like it worked. The cliffs: at sigma 0.016 the gain falls to 0.05 dB, and the gain over blur length peaks at 5 pixels, falling at both ends because a short blur has little to remove and a long one has destroyed the information.
Terms: Wiener — A linear filter that uses signal and noise statistics to minimize mean squared estimation error.
↑ The measurement ―― 固定した nsr で比べると、正しい PSF でもゼロ点に負ける。 (figure labels are in Japanese; the numbers are the same)
↑ σ=0 で +1.52 dB、σ=0.016 で +0.05 dB。
↑ 参照なし指標が選ぶ手法は PSNR で 3〜5 dB 劣る。
py -3.11 examples/poc_real_deblur_honesty.py
Source: examples/poc_real_deblur_honesty.py
This run produced 4 figures in total - see them all
Ops used (notes): iv_motion_deblur · iv_richardson_lucy · iv_unsharp_deblur · psnr · reflect · sk_blur_effect · unsharp
Observations made by downsampling the truth, with single-image upscaling, iterative back-projection and drizzle scored on a resolution table. No single-image upscaler beats the bicubic null by more than +0.036 dB. Drizzling 16 sub-pixel-shifted frames gains +13.96 dB in the undersampled condition, and the modulation of a period-6 pattern beyond Nyquist rises from 0.03 to 0.38.
↑ Does Super-Resolution Add Information? Downsample With the Truth in Hand, Restore, Count ―― Observations made by downsampling the truth, with single-image upscaling, iterative back-projection and drizzle scored on a resolution table. No single-image upscaler beats the bicubic null by more than +0.036 dB. Drizzling 16 sub-pixel-shifted frames gains +13.96 dB in the undersampled condition, and the modulation of a period-6 pattern beyond Nyquist rises from 0.03 to 0.38.
↑ The measurement ―― 鮮鋭化だけがナイキスト(周期 8)より細かい列にも縞を作る。それは分解能ではなく無い縞。 (figure labels are in Japanese; the numbers are the same)
↑ IBP は周期 12 以上の落ちた変調を戻すが、8 より細かい列は1 本も戻らない(順モデルの零空間)。
↑ 上限の線がレンズの許す限界。
↑ The animation ―― 動画(61 コマ、縞の群を 2 倍で表示): 前半は単一画像の IBP を 0 → 24 回。周期 12 の変調度は 0.78 → 0.96 と戻るが、ナイキスト(周期 8)より細かい列の最大は 0.03 → 0.04 にとどまる —— 順モデルの零空間に落ちた縞は何回回しても立たない(PSNR も 21.10 → 21.09 dB で動かない)。後半は標本化不足(σ = 0.30)の 16 枚を 1 枚ずつ drizzle に足す(ずれは相互相関で推定、pixfrac は枚数に合わせて 1.0 → 0.4、被覆の穴は単一画像の bicubic で埋めて割合を表示)。周期 6 の変調度は単一画像の 0.03 から 16 枚で 0.38 まで立ち上がる(レンズの上限 0.46)。PSNR は 28.27 → 42.24 dB。
py -3.11 examples/poc_superresolution_limits.py
Source: examples/poc_superresolution_limits.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · data_to_pixel · drizzle_resample · piv_cross_correlate · plot_series · psnr · ssim · text_box · ticks · unsharp · vol_fft_lowpass · vol_resize · volume_downsample
The first image-side PoC under the new rule of bringing the truth in from outside. Truth = TID2013 (Ponomarenko et al., 25 references × 24 distortions × 5 levels = 3,000 images, MOS from 971 observers); reference = the 14 metric value files the authors ship with the data (psnr.txt, ssim.txt, …) and their published rank-correlation table. The new module iqatid (11 ops): the authors’ luminance convention (BT.601 limited-range integer Y′, not stated on the page and found by measurement; full-range Y is 1.321 dB off), Spearman with average ranks and Kendall τ_b (these two reproduce all 14 published rows), the published table, the index reader with validation (3,000 rows, the name set must match the files on disk, case-insensitive), evaluation, comparison with the authors’ values and per-distortion correlation. Figures: MOS vs PSNR / SSIM scatter, SROCC per distortion type (local block distortion 0.15 / 0.63, contrast 0.44 / 0.45 and saturation 0.22 / 0.22 are weak), histograms of the differences to the authors’ values, a table of published / reproduced / Fullseye values for 14 metrics, and one reference–distorted pair with its SSIM map. 21 gates: closed forms for the rank correlations (monotone 1, reversed −1, hand-computed ties ρ = 8.5/√95 and τ_b = 7/√90, 1e-12 against the second implementation in graphinv), Y′ endpoints 16 / 235, PSNR of a 1-LSB shift = 48.13 dB, SSIM identity 1, the 14-row published table. Real data, 3,000 pairs in 110 s: Fullseye’s RGB PSNR matches the authors’ PSNRc to four decimals (max 0.00005 dB), luminance PSNR matches to 0.00017 dB on the 2,774 pairs outside reference I12 (I12’s 115 pairs have 28 pixels whose luminance lands exactly on .5 and round the other way, 0.00058 dB; reported as a separate gate rather than silently dropped), SSIM on Y′ to 0.000051, rank correlations PSNR 0.6395 / 0.4699 (published 0.640 / 0.470) and SSIM 0.6370 / 0.4635 (0.637 / 0.464), and the 14 authors’ rows reproduced to 0.00048. The authors write exact matches of luminance PSNR as 100000.0 (111 rows, all saturation changes). Honestly: the four-decimal agreement comes from following the same original implementation’s conventions (the old ssim_index.m: 11×11 Gaussian σ 1.5, borders cropped, no downsampling), not from SSIM being good (it ranks 10th of 14). FSIM / FSIMc / VIF / GMSD are not implemented; only their published values are tabulated. Images and MOS are not committed (research-and-education-only terms, FULLSEYE_TID2013_DATA). CI runs the synthetic gates only.
↑ Measuring Image-Quality Metrics Against a Public Ground Truth — TID2013’s 3,000 Images × 971 Observers and the Authors’ Published Values as the Gate ―― The first image-side PoC under the new rule of bringing the truth in from outside. Truth = TID2013 (Ponomarenko et al., 25 references × 24 distortions × 5 levels = 3,000 images, MOS from 971 observers); reference = the 14 metric value files the authors ship with the data (psnr.txt, ssim.txt, …) and their published rank-correlation table. The new module iqatid (11 ops): the authors’ luminance convention (BT.601 limited-range integer Y′, not stated on the page and found by measurement; full-range Y is 1.321 dB off), Spearman with average ranks and Kendall τ_b (these two reproduce all 14 published rows), the published table, the index reader with validation (3,000 rows, the name set must match the files on disk, case-insensitive), evaluation, comparison with the authors’ values and per-distortion correlation. Figures: MOS vs PSNR / SSIM scatter, SROCC per distortion type (local block distortion 0.15 / 0.63, contrast 0.44 / 0.45 and saturation 0.22 / 0.22 are weak), histograms of the differences to the authors’ values, a table of published / reproduced / Fullseye values for 14 metrics, and one reference–distorted pair with its SSIM map. 21 gates: closed forms for the rank correlations (monotone 1, reversed −1, hand-computed ties ρ = 8.5/√95 and τ_b = 7/√90, 1e-12 against the second implementation in graphinv), Y′ endpoints 16 / 235, PSNR of a 1-LSB shift = 48.13 dB, SSIM identity 1, the 14-row published table. Real data, 3,000 pairs in 110 s: Fullseye’s RGB PSNR matches the authors’ PSNRc to four decimals (max 0.00005 dB), luminance PSNR matches to 0.00017 dB on the 2,774 pairs outside reference I12 (I12’s 115 pairs have 28 pixels whose luminance lands exactly on .5 and round the other way, 0.00058 dB; reported as a separate gate rather than silently dropped), SSIM on Y′ to 0.000051, rank correlations PSNR 0.6395 / 0.4699 (published 0.640 / 0.470) and SSIM 0.6370 / 0.4635 (0.637 / 0.464), and the 14 authors’ rows reproduced to 0.00048. The authors write exact matches of luminance PSNR as 100000.0 (111 rows, all saturation changes). Honestly: the four-decimal agreement comes from following the same original implementation’s conventions (the old ssim_index.m: 11×11 Gaussian σ 1.5, borders cropped, no downsampling), not from SSIM being good (it ranks 10th of 14). FSIM / FSIMc / VIF / GMSD are not implemented; only their published values are tabulated. Images and MOS are not committed (research-and-education-only terms, FULLSEYE_TID2013_DATA). CI runs the synthetic gates only.
↑ The measurement ―― 971 人の MOS と Fullseye の PSNR(作者の輝度規約 Y′、psnr.txt と 4 桁一致)。順位相関は公表値どおり低い —— PSNR は歪み種が混ざると主観に合わない。 彩度変化で Y′ が変わらない 106 組(PSNR = inf、MOS 3.4〜6.0)は図に載らない(順位相関には最大の順位で入れてある)。 (figure labels are in Japanese; the numbers are the same)
↑ 同じ 3,000 組と Fullseye の SSIM(Wang 2004 の既定、ssim.txt と 4 桁一致)。
↑ 歪み種ごとの順位相関(各 25 参照 × 5 段)。
↑ 作者値との行ごとの差。
↑ 公表表(ページ / readme TABLE III・IV)と、同梱の作者値 + mos.txt から平均順位・τ_b で再現した値、Fullseye 実装がある 3 本の自前値。
py -3.11 examples/poc_iqa_tid2013.py
Source: examples/poc_iqa_tid2013.py
This run produced 6 figures in total - see them all
Ops used (notes): luma_limited_u8 · psnr · rank_data · rank_kendall_b · rank_spearman · ssim · ssim_map · tid2013_by_distortion · tid2013_compare · tid2013_evaluate · tid2013_index · tid2013_metric_values · tid2013_published · tid2013_root
| The second exhibit of the IQA series (the first covered PSNR / SSIM (structural similarity)). The first exhibit had listed FSIM / FSIMc / VIFP as ‘not implemented, published values only’; here they are implemented in numpy + scipy alone (new module iqafsim, 7 ops: fsim / fsimc / fsim_pair / gmsd / gmsd_map / vifp / phase_congruency_pc) and GMSD, absent from the distribution, is added. Primary sources are four papers: FSIM = Zhang, Zhang, Mou & Zhang, IEEE TIP 2011 (phase congruency after Kovesi 1999; his public code is MIT, so the noise-threshold heuristics come from there), GMSD = Xue, Zhang, Mou & Bovik, IEEE TIP 2014, VIF = Sheikh & Bovik, IEEE TIP 2006 in its pixel-domain form (the ‘VIFP’ row of the TID2013 paper; the steerable ‘VIF’ row is a different implementation and is not reproduced). The FSIM authors’ code is for research and education only, so nothing was ported: the index was written from the paper’s equations and only the constants were checked against §IV-A. The truth comes in three grades. (1) The author-value files FSIMc.txt / FSIM.txt / VIFP.txt (3,000 rows, four decimals), row by row: max | difference | 5.0e-5 / 5.0e-5 / 6.8e-5 — inside the rounding width. (2) The four-digit rank correlations of Tables 4/5 of the TID2013 paper: unrounded values give FSIM 0.8008 / 0.6295 (paper 0.8007 / 0.6300), FSIMc 0.8510 / 0.6665 (0.8510 / 0.6669), VIFP 0.6080 / 0.4560 (0.6084 / 0.4567); rounding our values to four decimals as the authors did reproduces all three on the full set and on the seven subsets (Noise / Actual / Simple / Exotic / New / Color / Full) with Δ 0.0000. (3) GMSD has no author file (the distribution is from 2013, GMSD from 2014); it is checked against a secondary source (Nafchi et al. 2016), | ρ | 0.8044 / 0.6339, to 0.0006 / 0.0005 — stated in the table as a grade lower. The input conventions were found by measurement (no page states them): FSIM.txt and VIFP.txt are computed on the same limited-range integer luma Y′ (16–235) as PSNR / SSIM, while FSIMc.txt is computed on the colour bitmaps via full-range YIQ. So FSIM.txt is not the FSIM component inside the FSIMc computation (measuring FSIM on the colour image’s Y misses FSIM.txt by up to 0.0318 — kept as a counter-example gate). Figures: four MOS scatter plots (SROCC and published value in the title), SROCC per distortion type for 24 types (under 18, colour saturation, the three luma-only indices collapse and only FSIMc survives), and a table with the grade of the truth as a column. 26 gates (10 synthetic, 6 on the default real subset, 10 more with –full): identities FSIM = FSIMc = 1, GMSD = 0, VIF = 1; monotonic in noise σ (rank correlation ∓1); FSIM / GMSD symmetric and VIF asymmetric (it divides by the reference’s information); phase-congruency gain invariance (1e-10 with ε = 1e-12; the 2.8e-5 residual at the default ε = 1e-4 scales exactly with ε); the even-kernel ‘same’ convention; round(1.5) = 2; correlation 0.66 with the existing monogenic phase congruency as a second implementation; GMS range and VIF > 1 under contrast enhancement (the authors’ maximum 1.1379 is also distortion 17); fail-closed; agreement of the four-digit and three-digit published tables; on real data the three row-wise gates, the counter-example, GMSD’s negative correlation and the per-type table. Default is the first 120 pairs (reference I01, 24 types × 5 levels) in 30 s; –full runs all 3,000 pairs in 11 minutes. Honestly: four-digit agreement comes from following the same conventions, not from the indices’ merit (FSIMc 0.851 ranks first, VIFP 0.608 thirteenth); GMSD is checked only against a secondary source; steerable VIF is not implemented; distortions 16–18 are weak for all four. Traps: scipy’s even-kernel ‘same’ is half a pixel off (a 2×2 average gives a different answer), round(1.5) goes away from zero, ε belongs in the unit-vector normalisation only (in the denominator it breaks gain invariance by 3e-5), GMSD’s sign (the published tables list | ρ | ; the first run failed by −1.6), and the paper’s four-digit tables are computed from the authors’ four-decimal text files (rounding ties 106–122 pairs of distortion 18 and moves the New / Color columns in the fourth digit). |
| *↑ Perceptual Indices FSIM (feature similarity index) / FSIMc (colour extension of FSIM) / GMSD (gradient-similarity deviation) / VIF Matched to Four Digits Against an External Truth — TID2013’s Three Author-Value Files and the Published Rank-Correlation Tables ―― The second exhibit of the IQA series (the first covered PSNR / SSIM (structural similarity)). The first exhibit had listed FSIM / FSIMc / VIFP as ‘not implemented, published values only’; here they are implemented in numpy + scipy alone (new module iqafsim, 7 ops: fsim / fsimc / fsim_pair / gmsd / gmsd_map / vifp / phase_congruency_pc) and GMSD, absent from the distribution, is added. Primary sources are four papers: FSIM = Zhang, Zhang, Mou & Zhang, IEEE TIP 2011 (phase congruency after Kovesi 1999; his public code is MIT, so the noise-threshold heuristics come from there), GMSD = Xue, Zhang, Mou & Bovik, IEEE TIP 2014, VIF = Sheikh & Bovik, IEEE TIP 2006 in its pixel-domain form (the ‘VIFP’ row of the TID2013 paper; the steerable ‘VIF’ row is a different implementation and is not reproduced). The FSIM authors’ code is for research and education only, so nothing was ported: the index was written from the paper’s equations and only the constants were checked against §IV-A. The truth comes in three grades. (1) The author-value files FSIMc.txt / FSIM.txt / VIFP.txt (3,000 rows, four decimals), row by row: max | difference | 5.0e-5 / 5.0e-5 / 6.8e-5 — inside the rounding width. (2) The four-digit rank correlations of Tables 4/5 of the TID2013 paper: unrounded values give FSIM 0.8008 / 0.6295 (paper 0.8007 / 0.6300), FSIMc 0.8510 / 0.6665 (0.8510 / 0.6669), VIFP 0.6080 / 0.4560 (0.6084 / 0.4567); rounding our values to four decimals as the authors did reproduces all three on the full set and on the seven subsets (Noise / Actual / Simple / Exotic / New / Color / Full) with Δ 0.0000. (3) GMSD has no author file (the distribution is from 2013, GMSD from 2014); it is checked against a secondary source (Nafchi et al. 2016), | ρ | 0.8044 / 0.6339, to 0.0006 / 0.0005 — stated in the table as a grade lower. The input conventions were found by measurement (no page states them): FSIM.txt and VIFP.txt are computed on the same limited-range integer luma Y′ (16–235) as PSNR / SSIM, while FSIMc.txt is computed on the colour bitmaps via full-range YIQ. So FSIM.txt is not the FSIM component inside the FSIMc computation (measuring FSIM on the colour image’s Y misses FSIM.txt by up to 0.0318 — kept as a counter-example gate). Figures: four MOS scatter plots (SROCC and published value in the title), SROCC per distortion type for 24 types (under 18, colour saturation, the three luma-only indices collapse and only FSIMc survives), and a table with the grade of the truth as a column. 26 gates (10 synthetic, 6 on the default real subset, 10 more with –full): identities FSIM = FSIMc = 1, GMSD = 0, VIF = 1; monotonic in noise σ (rank correlation ∓1); FSIM / GMSD symmetric and VIF asymmetric (it divides by the reference’s information); phase-congruency gain invariance (1e-10 with ε = 1e-12; the 2.8e-5 residual at the default ε = 1e-4 scales exactly with ε); the even-kernel ‘same’ convention; round(1.5) = 2; correlation 0.66 with the existing monogenic phase congruency as a second implementation; GMS range and VIF > 1 under contrast enhancement (the authors’ maximum 1.1379 is also distortion 17); fail-closed; agreement of the four-digit and three-digit published tables; on real data the three row-wise gates, the counter-example, GMSD’s negative correlation and the per-type table. Default is the first 120 pairs (reference I01, 24 types × 5 levels) in 30 s; –full runs all 3,000 pairs in 11 minutes. Honestly: four-digit agreement comes from following the same conventions, not from the indices’ merit (FSIMc 0.851 ranks first, VIFP 0.608 thirteenth); GMSD is checked only against a secondary source; steerable VIF is not implemented; distortions 16–18 are weak for all four. Traps: scipy’s even-kernel ‘same’ is half a pixel off (a 2×2 average gives a different answer), round(1.5) goes away from zero, ε belongs in the unit-vector normalisation only (in the denominator it breaks gain invariance by 3e-5), GMSD’s sign (the published tables list | ρ | ; the first run failed by −1.6), and the paper’s four-digit tables are computed from the authors’ four-decimal text files (rounding ties 106–122 pairs of distortion 18 and moves the New / Color columns in the fourth digit).* |
Terms: FSIM — An image-similarity index using phase congruency as the primary feature and gradient magnitude as a secondary feature.; FSIMc — The colour extension of FSIM, adding chromatic-difference terms to the feature-similarity score.; GMSD — An image-quality metric defined as the standard deviation of a gradient-magnitude similarity map between reference and test images.; SSIM — An image-quality index comparing local luminance, contrast, and structure between two images.; VIFP — A pixel-domain image-quality metric measuring the fraction of reference information preserved through a natural-image and visual-system model.
↑ The measurement ―― FSIMC と 971 人の MOS(3,000 組)。作者値 FSIMc.txt と 4 桁一致(色画像 → YIQ full range)。TID2013 全体で 1 位の指標。 (figure labels are in Japanese; the numbers are the same)
↑ VIFP と 971 人の MOS(3,000 組)。
↑ GMSD と 971 人の MOS(3,000 組)。
↑ 歪み種ごとの順位相関。
↑ 真値の等級を列にした: FSIM / FSIMc / VIFP は作者値ファイル(4 桁)と行ごとに比べられる。
py -3.11 examples/poc_iqa_fsim_gmsd_vif.py
Source: examples/poc_iqa_fsim_gmsd_vif.py
This run produced 6 figures in total - see them all
Ops used (notes): fsim · fsim_pair · fsimc · gmsd · gmsd_map · phase_congruency_pc · rank_kendall_b · rank_spearman · tf_phase_congruency · tid2013_by_distortion · tid2013_compare · tid2013_evaluate · tid2013_index · tid2013_metric_values · tid2013_published · tid2013_root · vifp
A vehicle whose heading drifts under a disturbance, carrying nothing but a compound eye and closed-form circuitry, holds its course: the fly’s optomotor response built out of fullseye operators alone and measured next to a ground-truth gyro. Every stage is a closed form and nothing is trained: hexagonal sampling (fly_hex_resample), lamina adaptation and band-pass (fly_lamina_filter), the ON/OFF split (fly_onoff_split), direction selectivity in all six lattice directions (fly_t4t5_field), the local flow (fly_flow_from_directions), a linear fit against the matched filter (fly_matched_filter / fly_egomotion_from_flow), and a six-neuron, eight-synapse steering circuit run as conductances (graph_conductance_states). Each stage has an exact identity: brightening the whole scene ten thousand times leaves the lamina output unchanged (Weber, 7e-16), the three-arm T4 model with Haag et al. 2016’s verbatim constants (tau = 250 ms, k = 5/5/10) returns the paper’s 24.9636 and 0.8195 on its two-column stimulus, and the paper’s claim that the two mechanisms are complementary becomes a product identity (4.161 x 7.321 = 30.461 = the whole model) that holds to machine precision; the matched filter’s 1.7 rad/s comes back to nine digits. The limits are measured too: on the classical striped drum the estimate tracks a time-varying rotation at 0.976 correlation, in a natural 1/f scene at 0.897, and the single calibration gain it needs is 2.64 on the drum against 6.76 and 7.88 in natural scenes - 3.0x between kinds of scene and 1.16x between two scenes of identical statistics, because a correlation detector reports contrast-weighted motion rather than velocity. A single 80-degree eye measuring the same +0.5 rad/s across twelve scenes scatters by 1.05 of its mean and gets the sign of the rotation wrong in four of them; merging three eyes into 250 degrees with fly_eye_merge drops the scatter to 0.44 and the sign errors to zero - the width of a compound eye is not decoration. In closed loop (calibrated in scene A, flown in an unseen scene B) the open loop drifts 51 degrees, the visual reflex 33, and the ground-truth gyro 23: a reflex has no absolute heading and cannot null the drift, which is where the central complex would come in. Writing the steering on the connectome side gives the same 33 degrees, and the membrane potential is a convex combination of the reversal potentials so it cannot diverge. No data ships; the scenes are generated from seeds by fly_sky_1f.
↑ Holding a Course with a Fly’s Optic Lobe Alone — From the Lamina to the Steering, Without Learning ―― A vehicle whose heading drifts under a disturbance, carrying nothing but a compound eye and closed-form circuitry, holds its course: the fly’s optomotor response built out of fullseye operators alone and measured next to a ground-truth gyro. Every stage is a closed form and nothing is trained: hexagonal sampling (fly_hex_resample), lamina adaptation and band-pass (fly_lamina_filter), the ON/OFF split (fly_onoff_split), direction selectivity in all six lattice directions (fly_t4t5_field), the local flow (fly_flow_from_directions), a linear fit against the matched filter (fly_matched_filter / fly_egomotion_from_flow), and a six-neuron, eight-synapse steering circuit run as conductances (graph_conductance_states). Each stage has an exact identity: brightening the whole scene ten thousand times leaves the lamina output unchanged (Weber, 7e-16), the three-arm T4 model with Haag et al. 2016’s verbatim constants (tau = 250 ms, k = 5/5/10) returns the paper’s 24.9636 and 0.8195 on its two-column stimulus, and the paper’s claim that the two mechanisms are complementary becomes a product identity (4.161 x 7.321 = 30.461 = the whole model) that holds to machine precision; the matched filter’s 1.7 rad/s comes back to nine digits. The limits are measured too: on the classical striped drum the estimate tracks a time-varying rotation at 0.976 correlation, in a natural 1/f scene at 0.897, and the single calibration gain it needs is 2.64 on the drum against 6.76 and 7.88 in natural scenes - 3.0x between kinds of scene and 1.16x between two scenes of identical statistics, because a correlation detector reports contrast-weighted motion rather than velocity. A single 80-degree eye measuring the same +0.5 rad/s across twelve scenes scatters by 1.05 of its mean and gets the sign of the rotation wrong in four of them; merging three eyes into 250 degrees with fly_eye_merge drops the scatter to 0.44 and the sign errors to zero - the width of a compound eye is not decoration. In closed loop (calibrated in scene A, flown in an unseen scene B) the open loop drifts 51 degrees, the visual reflex 33, and the ground-truth gyro 23: a reflex has no absolute heading and cannot null the drift, which is where the central complex would come in. Writing the steering on the connectome side gives the same 33 degrees, and the membrane potential is a convex combination of the reversal potentials so it cannot diverge. No data ships; the scenes are generated from seeds by fly_sky_1f.
↑ The measurement ―― left to right, top to bottom: what the ommatidia see, the lamina’s contrast (brightness thrown away), the ON and OFF channels, and the direction-selective field read as a local flow. The eye is turning at 0.5 rad/s in a 1/f panorama; nothing here was trained. (figure labels are in Japanese; the numbers are the same)
↑ each scene is fitted with its own single gain; the striped drum is nearly linear, and a natural 1/f scene needs a gain 3.0x larger — a correlation det…
↑ a narrow eye is at the mercy of whichever few large features are in front of it: over eight scenes of identical statistics it scatters and gets the si…
↑ the disturbance has a steady bias, so the open loop drifts away; the optomotor reflex cuts the drift but cannot null it — a reflex has no absolute hea…
↑ the wiring is written by hand as a synapse table and run as a conductance circuit; the state is a convex combination of the reversal potentials, so it…
py -3.11 examples/poc_fly_optomotor_steering.py
Source: examples/poc_fly_optomotor_steering.py
This run produced 7 figures in total - see them all
Ops used (notes): fly_egomotion_from_flow · fly_eye_merge · fly_flow_from_directions · fly_hex_lattice · fly_hex_resample · fly_lamina_filter · fly_matched_filter · fly_onoff_split · fly_sky_1f · fly_t4t5_field · graph_conductance_states · graph_from_synapses
Both a picture that mixes high and low frequencies and a shape that morphs smoothly carry exact truths that can be measured straight off the image - and the customary way of making them is the one that misses those truths. The first concerns orthogonality. Splitting an image into low and high parts with an ideal frequency mask makes the two exactly orthogonal, so the energies add: across four cut-off frequencies the relative discrepancy in E(low) + E(high) - E(original) is 1.9e-16. The textbook construction - blur with a Gaussian, call the remainder the high part - misses by 0.53 % to 3.10 %, even though both reconstruct the original exactly when added back: being invertible and being orthogonal are different properties. The discrepancy is also not monotone in the blur: the worst case is the weakest blur and the best is in the middle of the range, so neither strengthening nor weakening it fixes the problem. The second concerns distance. Binning k pixels - what moving away does - has the transfer function sin(pi f k)/(k sin(pi f)), which is exactly zero when f times k is an integer, so a pattern of period p pixels vanishes exactly when binned at p pixels (1.4e-14 over five periods). More strongly, the binned picture can be written pixel by pixel in closed form - the amplitude and the fact that the binned pixel’s centre shifts by (k-1)/2 - agreeing to 2.8e-14 over every pixel for bin widths 1 through 48. The third concerns shape. Interpolating polygon vertices linearly makes the enclosed area an exact quadratic in t, so three frames predict all of them (2.4e-15 across three shapes), and this survives measurement from the picture: rasterising the polygon and counting brings the deviation from 1.10 % on a 128 grid down to 0.053 % on a 512 grid with each pixel split four ways - and splitting a pixel into 4x4 gives exactly the same number as using a four times finer grid. The fourth is where measurement from a picture breaks. Morphing to the same polygon with its vertices reversed makes the signed area linear in t, vanishing at t = 0.5 where the polygon collapses to a line, and the quadratic prediction holds to 3.4e-15; but a picture only yields positive area, so the parabola folds and the three-frame prediction is off by exactly one quarter of the maximum - measured at 25.0 % for two different shapes. Even the size of the error has a closed form. The fifth pairs two exact spline properties: Catmull-Rom passes through its control points at distance 0.0e+00, while a Bezier curve does not pass through them but never leaves their convex hull (401 of 401 points across four sets) - exactly complementary guarantees. Three predictions were wrong and have been left in: that the bin width at which a hybrid image switches could be predicted from the cut-off frequency as 1/(2 fc), when in fact tripling the cut-off barely moves the measured switch because what governs it is the period the high-frequency picture actually contains; that the Gaussian discrepancy would grow with the blur; and that reading amplitude off the picture’s maximum and minimum would match the closed form, when at a bin width of six it reads 20.7 % low because the pixels never land on the crest - the formula is right even where no pixel sits. No new operator was added.
↑ The Exact Distance at Which Detail Vanishes, and the Area of a Changing Shape ―― Both a picture that mixes high and low frequencies and a shape that morphs smoothly carry exact truths that can be measured straight off the image - and the customary way of making them is the one that misses those truths. The first concerns orthogonality. Splitting an image into low and high parts with an ideal frequency mask makes the two exactly orthogonal, so the energies add: across four cut-off frequencies the relative discrepancy in E(low) + E(high) - E(original) is 1.9e-16. The textbook construction - blur with a Gaussian, call the remainder the high part - misses by 0.53 % to 3.10 %, even though both reconstruct the original exactly when added back: being invertible and being orthogonal are different properties. The discrepancy is also not monotone in the blur: the worst case is the weakest blur and the best is in the middle of the range, so neither strengthening nor weakening it fixes the problem. The second concerns distance. Binning k pixels - what moving away does - has the transfer function sin(pi f k)/(k sin(pi f)), which is exactly zero when f times k is an integer, so a pattern of period p pixels vanishes exactly when binned at p pixels (1.4e-14 over five periods). More strongly, the binned picture can be written pixel by pixel in closed form - the amplitude and the fact that the binned pixel’s centre shifts by (k-1)/2 - agreeing to 2.8e-14 over every pixel for bin widths 1 through 48. The third concerns shape. Interpolating polygon vertices linearly makes the enclosed area an exact quadratic in t, so three frames predict all of them (2.4e-15 across three shapes), and this survives measurement from the picture: rasterising the polygon and counting brings the deviation from 1.10 % on a 128 grid down to 0.053 % on a 512 grid with each pixel split four ways - and splitting a pixel into 4x4 gives exactly the same number as using a four times finer grid. The fourth is where measurement from a picture breaks. Morphing to the same polygon with its vertices reversed makes the signed area linear in t, vanishing at t = 0.5 where the polygon collapses to a line, and the quadratic prediction holds to 3.4e-15; but a picture only yields positive area, so the parabola folds and the three-frame prediction is off by exactly one quarter of the maximum - measured at 25.0 % for two different shapes. Even the size of the error has a closed form. The fifth pairs two exact spline properties: Catmull-Rom passes through its control points at distance 0.0e+00, while a Bezier curve does not pass through them but never leaves their convex hull (401 of 401 points across four sets) - exactly complementary guarantees. Three predictions were wrong and have been left in: that the bin width at which a hybrid image switches could be predicted from the cut-off frequency as 1/(2 fc), when in fact tripling the cut-off barely moves the measured switch because what governs it is the period the high-frequency picture actually contains; that the Gaussian discrepancy would grow with the blur; and that reading amplitude off the picture’s maximum and minimum would match the closed form, when at a bin width of six it reads 20.7 % low because the pixels never land on the crest - the formula is right even where no pixel sits. No new operator was added.
↑ The measurement ―― 低い周波数に粗い模様、高い周波数に細かい模様を入れた 1 枚を、束ねながら見たもの。束ねるのは「遠ざかる」ことで、細かいほうが先に消える。ただし ★いつ消えるかは遮断周波数では決まらない —— 下の数表の「外した予言」を参照。 (figure labels are in Japanese; the numbers are the same)
↑ どちらも足せば元に戻る(再構成の誤差は両方 1e-13 未満)。
↑ k = 24 と k = 48(f·k が整数)で 厳密に 0、その間では戻る。
↑ 閉形式なら 2.4e-15。
↑ 薄い折れ線が制御点をつないだもの、濃い線が Catmull-Rom。
↑ The animation ―― 6 角形が別の 6 角形へ変わって戻るところ。★見た目は連続に変わるが、囲む面積は t の 厳密な 2 次式に乗っている(次の図)。
py -3.11 examples/poc_vanishing_detail_and_morphing_area.py
Source: examples/poc_vanishing_detail_and_morphing_area.py
This run produced 14 figures in total - see them all
Ops used (notes): convex_hull · gauss_filter · morph
Before adding segmentation methods, the measures and the ground truth come first — new modules segeval (contingency table, Dice (region-overlap coefficient)/Jaccard (set-overlap coefficient), boundary F, Hausdorff (maximum nearest-set distance)/ASSD, over/under-segmentation, object-count matching, a score card; each op gated by an identity or a second implementation) and segworld (six synthetic worlds with ground truth: touching equal-radius blobs with the closed-form lens area, Voronoi grains whose boundary length matches scipy’s ridges to 1e-9, parts with shadows, same-mean regions differing only in texture, an illumination gradient, and 1–3 px thin structures). Ten existing methods (Otsu, local threshold, Niblack, Sauvola, Chan–Vese, random walker, two watersheds, Felzenszwalb, SLIC) run with default knobs across every world. Gates: truth vs truth scores perfectly on every measure in all six worlds / mask measures are blind to merging (Otsu scores J=0.95 while 9 of 10 blobs are merged) / boundary measures are blind to over-segmentation (gradient watershed BF=1.00 with 339 over-segmented) / Sauvola gets every grain count right but drops the boundary band, J=0.91 / on parts with shadows every method scores J < 0.3, and even a threshold oracle that sees the truth only J=0.29 / textures: mask methods ARI < 0.3, local-σ k-means ARI=0.92 / illumination gradient: global Otsu J=0.21, flat-fielded J=1.00 / thin structures: Otsu’s boundary F is 1.00 yet noise grains over-segment 29. Honestly: method knobs are fixed at defaults (some would recover if tuned), boundary F is the distance-transform BF rather than Martin 2004’s bipartite matching, and the edge condition in count matching is our own definition. 10 gates, 2.0 s.
↑ The Segmentation Gauntlet — Ten Methods Across Six Worlds With Ground Truth, and Which Measure Is Blind to Which Failure ―― Before adding segmentation methods, the measures and the ground truth come first — new modules segeval (contingency table, Dice (region-overlap coefficient)/Jaccard (set-overlap coefficient), boundary F, Hausdorff (maximum nearest-set distance)/ASSD, over/under-segmentation, object-count matching, a score card; each op gated by an identity or a second implementation) and segworld (six synthetic worlds with ground truth: touching equal-radius blobs with the closed-form lens area, Voronoi grains whose boundary length matches scipy’s ridges to 1e-9, parts with shadows, same-mean regions differing only in texture, an illumination gradient, and 1–3 px thin structures). Ten existing methods (Otsu, local threshold, Niblack, Sauvola, Chan–Vese, random walker, two watersheds, Felzenszwalb, SLIC) run with default knobs across every world. Gates: truth vs truth scores perfectly on every measure in all six worlds / mask measures are blind to merging (Otsu scores J=0.95 while 9 of 10 blobs are merged) / boundary measures are blind to over-segmentation (gradient watershed BF=1.00 with 339 over-segmented) / Sauvola gets every grain count right but drops the boundary band, J=0.91 / on parts with shadows every method scores J < 0.3, and even a threshold oracle that sees the truth only J=0.29 / textures: mask methods ARI < 0.3, local-σ k-means ARI=0.92 / illumination gradient: global Otsu J=0.21, flat-fielded J=1.00 / thin structures: Otsu’s boundary F is 1.00 yet noise grains over-segment 29. Honestly: method knobs are fixed at defaults (some would recover if tuned), boundary F is the distance-transform BF rather than Martin 2004’s bipartite matching, and the edge condition in count matching is our own definition. 10 gates, 2.0 s.
Terms: Dice — A 0-to-1 overlap score equal to twice the intersection size divided by the sum of the two region sizes.; Jaccard — A 0-to-1 set-similarity score equal to intersection size divided by union size.; Hausdorff — A distance between two point sets given by the larger of their two directed maximum nearest-neighbour distances.; Otsu — A histogram thresholding method that chooses the threshold maximizing the variance between the two resulting classes.; Niblack — A local thresholding method that sets each pixel’s threshold from the mean and standard deviation in its neighbourhood.; Sauvola — A document-oriented refinement of Niblack thresholding that uses the local mean and a normalized local standard deviation.
↑ The measurement ―― Jaccard は物体マスク(格子を返す手法は —)。VI = Meilă の情報の変分(0 が一致)、ARI = 補正 Rand、境界 F は τ = 2 px、HD95 = 境界の Hausdorff の 95 % 点。過分割/未分割は分割表の多数決の多重度、一致/分裂/融合/欠落/偽は主に重なる辺の次数。最後の行はルールの直し方。 (figure labels are in Japanese; the numbers are the same)
↑ Jaccard は物体マスク(格子を返す手法は —)。
↑ Jaccard は物体マスク(格子を返す手法は —)。
↑ Jaccard は物体マスク(格子を返す手法は —)。
↑ Jaccard は物体マスク(格子を返す手法は —)。
↑ The animation ―― 触れ合う粒: 緑 = 真値の境界、赤 = 手法の境界、黄 = 一致。1 コマ = 1 手法(otsu, local_threshold, sk_niblack, sk_sauvola, sk_chan_vese, xsk_random_walker, watersheds, sg_watershed_gradient, sk_felzenszwalb, sk_slic, edt_watershed)。
↑ The animation ―― 結晶粒(ボロノイ): 緑 = 真値の境界、赤 = 手法の境界、黄 = 一致。1 コマ = 1 手法(otsu, local_threshold, sk_niblack, sk_sauvola, sk_chan_vese, xsk_random_walker, watersheds, sg_watershed_gradient, sk_felzenszwalb, sk_slic)。
↑ The animation ―― 影のある部品: 緑 = 真値の境界、赤 = 手法の境界、黄 = 一致。1 コマ = 1 手法(otsu, local_threshold, sk_niblack, sk_sauvola, sk_chan_vese, xsk_random_walker, watersheds, sg_watershed_gradient, sk_felzenszwalb, sk_slic)。
↑ The animation ―― 質感だけ違う領域: 緑 = 真値の境界、赤 = 手法の境界、黄 = 一致。1 コマ = 1 手法(otsu, local_threshold, sk_niblack, sk_sauvola, sk_chan_vese, xsk_random_walker, watersheds, sg_watershed_gradient, sk_felzenszwalb, sk_slic, texture_kmeans)。
↑ The animation ―― 照明の勾配: 緑 = 真値の境界、赤 = 手法の境界、黄 = 一致。1 コマ = 1 手法(otsu, local_threshold, sk_niblack, sk_sauvola, sk_chan_vese, xsk_random_walker, watersheds, sg_watershed_gradient, sk_felzenszwalb, sk_slic, flat_field_otsu)。
↑ The animation ―― 細い構造: 緑 = 真値の境界、赤 = 手法の境界、黄 = 一致。1 コマ = 1 手法(otsu, local_threshold, sk_niblack, sk_sauvola, sk_chan_vese, xsk_random_walker, watersheds, sg_watershed_gradient, sk_felzenszwalb, sk_slic)。
py -3.11 examples/poc_segmentation_gauntlet.py
Source: examples/poc_segmentation_gauntlet.py
This run produced 13 figures in total - see them all
Ops used (notes): blob_distance · blob_label · local_threshold · otsu · seg_dice_jaccard · seg_score_card · sg_watershed_gradient · sk_chan_vese · sk_felzenszwalb · sk_niblack · sk_sauvola · sk_slic · text_box · watersheds · world_blobs_touching · world_gradient_illumination · world_grains_voronoi · world_parts_with_shadow · world_texture_regions · world_thin_structures · xsk_random_walker
| Segmentation expansion, batch 2 — new module segcontour, 10 ops (snake, gradient vector flow, Chan–Vese energy and evolution, morphological Chan–Vese, morphological geodesic active contour with the edge-stopping function, signed-distance reinitialisation, DRLSE (distance-regularized level-set evolution), mean curvature flow). Each op is gated by a closed form or a second implementation: with no external force a circular snake shrinks exactly as the circulant-matrix eigenvalues say (error 3e-14) and matches skimage’s active_contour to 1e-13 / curvature flow loses area at dA/dt = −2π (circle −6.2767, a concave star −6.2822) / after reinitialisation | ∇φ | sits at 0.987–1.012 (10–90 %) and the zero level set does not move / the classical snake’s energy never rises (γ ≥ L, 0 increases), and Chan–Vese (alternating minimisation of the convex relaxation) is monotone in all four worlds / Dice 0.995 against the existing op sk_chan_vese. Scored with batch 1’s segeval and segworld: the classical snake cannot enter a U-shaped concavity (it bridges it completely and also floats off the flat sides — the edge-gradient force only reaches near edges), while the same α, β, γ with a GVF external force goes in (1.0 % bridged, Dice 0.993) / under an illumination gradient global two-mean Chan–Vese loses (J = 0.187) and edge-stopping local methods win (geodesic AC 0.960, DRLSE 0.938) / touching blobs score Dice 0.97 as a mask yet all 10 merge into one / on shadowed parts every contour method stays below J 0.7. Honestly: Chan–Vese 2001 does not claim monotone descent (the gate is on the convex side); the GVF, morphological-snake, DRLSE and Sussman equations were not checked against the original papers; DRLSE’s energy rose 247 times on the gradient world (explicit steps, not gated). 12 gates, 6.4 s. |
| *↑ Active Contours and Level Sets — Checking With Closed Forms How a Contour Shrinks, Enters a Concavity and Stops at an Edge ―― Segmentation expansion, batch 2 — new module segcontour, 10 ops (snake, gradient vector flow, Chan–Vese energy and evolution, morphological Chan–Vese, morphological geodesic active contour with the edge-stopping function, signed-distance reinitialisation, DRLSE (distance-regularized level-set evolution), mean curvature flow). Each op is gated by a closed form or a second implementation: with no external force a circular snake shrinks exactly as the circulant-matrix eigenvalues say (error 3e-14) and matches skimage’s active_contour to 1e-13 / curvature flow loses area at dA/dt = −2π (circle −6.2767, a concave star −6.2822) / after reinitialisation | ∇φ | sits at 0.987–1.012 (10–90 %) and the zero level set does not move / the classical snake’s energy never rises (γ ≥ L, 0 increases), and Chan–Vese (alternating minimisation of the convex relaxation) is monotone in all four worlds / Dice 0.995 against the existing op sk_chan_vese. Scored with batch 1’s segeval and segworld: the classical snake cannot enter a U-shaped concavity (it bridges it completely and also floats off the flat sides — the edge-gradient force only reaches near edges), while the same α, β, γ with a GVF external force goes in (1.0 % bridged, Dice 0.993) / under an illumination gradient global two-mean Chan–Vese loses (J = 0.187) and edge-stopping local methods win (geodesic AC 0.960, DRLSE 0.938) / touching blobs score Dice 0.97 as a mask yet all 10 merge into one / on shadowed parts every contour method stays below J 0.7. Honestly: Chan–Vese 2001 does not claim monotone descent (the gate is on the convex side); the GVF, morphological-snake, DRLSE and Sussman equations were not checked against the original papers; DRLSE’s energy rose 247 times on the gradient world (explicit steps, not gated). 12 gates, 6.4 s.* |
Terms: GVF — An external force for active contours that diffuses image gradients into a smooth vector field, extending capture range and reaching concavities.; DRLSE — A level-set evolution method whose distance regularization keeps the level-set function near a signed-distance function without reinitialization.; Dice — A 0-to-1 overlap score equal to twice the intersection size divided by the sum of the two region sizes.
↑ The measurement ―― c の更新(内外の平均)と、c を固定した凸緩和(Chan–Esedoglu–Nikolova)の交互最小化。どの世界でも1 度も増えず、分割が変わらなくなった所で止まる(門 5)。止まった所が真値に近いかは別(表を見る)。 (figure labels are in Japanese; the numbers are the same)
↑ γ = 1 ≥ L = 0.463(外力の勾配の Lipschitz 定数)なので降下補題により全体は増えない(実測 増加 0 回)。
↑ dA/dt = −∮κ ds = −2π。
↑ Jaccard/Dice は物体マスク、境界 F は τ = 2 px。
↑ The animation ―― U 字の凹部: 同じ α・β・γ で外力だけ違う。古典(赤)は凹部の口に橋を架けて止まり、辺の途中も外に浮いたまま(エッジの勾配の力は辺の近くにしか届かない)、GVF(青)は奥まで入る。緑 = 真の縁。1 コマ = 20 反復。
↑ The animation ―― 照明の勾配の上の暗い物体(反転して渡す)。全画面の矩形から 4 手法の輪郭が動く。大域の 2 平均(Chan–Vese・形態学的 CV)は明るい側の背景ごと切り、エッジで止まる GAC・DRLSE は物体に貼り付く(GAC は雑音の粒を数十個残す = 表の予測の個数)。緑 = 真の縁、各手法のコマは反復数に比例して間引いて 40 コマに揃えた。
↑ The animation ―― 凹んだ星形の平均曲率流。凹部は外へ、凸部は内へ動き、丸くなりながら面積は毎時間 2π ずつ減る。
py -3.11 examples/poc_active_contours.py
Source: examples/poc_active_contours.py
This run produced 8 figures in total - see them all
Ops used (notes): chan_vese_evolve · curvature_flow · drle_evolve · edge_stop_g · gvf_field · level_set_reinit · morph_chan_vese · morph_geodesic_ac · seg_dice_jaccard · seg_score_card · sk_chan_vese · snake_evolve · text_box · world_blobs_touching · world_gradient_illumination · world_parts_with_shadow · world_thin_structures
Segmentation expansion, batch 3 — new module seggraph, 16 ops. Gates: graph cut matches the brute-force minimum on 40 problems of 3×4 pixels, and max flow = min cut (exactly, in integers, even at 160×160) / α-expansion stays within E ≤ 2c·E* on all 80 problems (Boykov–Veksler–Zabih 2001, Theorem 6.1), and a 3×3 trap shows it can stop at a local optimum / area opening on the component tree is idempotent and anti-extensive and matches skimage pixel for pixel / quasi-flat zones = connected components of the minimum spanning tree cut at α (six values of α), nested as α grows / the hierarchical watershed has 0 ultrametric violations over all triples of 107 basins, and the surviving basins are those with dynamics > θ / every SNIC superpixel is 4-connected and its boundary recall is within 0.05 of SLIC / isodata is a fixed point, triangle matches skimage, Kapur is the brute-force maximum. Which method wins where: on touching blobs the hierarchical watershed gives 19 regions at θ = 0 (over-segmented), exactly 10 blobs for θ = 0.5–3.5, and one region at large θ / graph cut at λ = 0.1 raises Dice on noisy blobs by +0.13 and lowers it on 1–3 px lines by −0.13 at the same λ / SRM gets down to 0.74–0.90 bit of VI on grains but on same-mean texture regions never beats a single region at any q / under an illumination gradient Kittler scores Dice 0.93–0.99 and isodata 0.29–0.41. Three sweep videos (θ, q, λ). Honestly: SRM’s predicate constants, the original conventions of Kittler, Kapur and the triangle method, and SNIC’s equation (1) were not checked against the papers. 13 gates, 6.4 s.
↑ Graph, Hierarchical and Threshold Segmentation — Max Flow = Min Cut, Ultrametrics, and Turning a Knob From Coarse to Fine ―― Segmentation expansion, batch 3 — new module seggraph, 16 ops. Gates: graph cut matches the brute-force minimum on 40 problems of 3×4 pixels, and max flow = min cut (exactly, in integers, even at 160×160) / α-expansion stays within E ≤ 2c·E on all 80 problems (Boykov–Veksler–Zabih 2001, Theorem 6.1), and a 3×3 trap shows it can stop at a local optimum / area opening on the component tree is idempotent and anti-extensive and matches skimage pixel for pixel / quasi-flat zones = connected components of the minimum spanning tree cut at α (six values of α), nested as α grows / the hierarchical watershed has 0 ultrametric violations over all triples of 107 basins, and the surviving basins are those with dynamics > θ / every SNIC superpixel is 4-connected and its boundary recall is within 0.05 of SLIC / isodata is a fixed point, triangle matches skimage, Kapur is the brute-force maximum. Which method wins where: on touching blobs the hierarchical watershed gives 19 regions at θ = 0 (over-segmented), exactly 10 blobs for θ = 0.5–3.5, and one region at large θ / graph cut at λ = 0.1 raises Dice on noisy blobs by +0.13 and lowers it on 1–3 px lines by −0.13 at the same λ / SRM gets down to 0.74–0.90 bit of VI on grains but on same-mean texture regions never beats a single region at any q / under an illumination gradient Kittler scores Dice 0.93–0.99 and isodata 0.29–0.41. Three sweep videos (θ, q, λ). Honestly: SRM’s predicate constants, the original conventions of Kittler, Kapur and the triangle method, and SNIC’s equation (1) were not checked against the papers. 13 gates, 6.4 s.*
Terms: SNIC — A priority-queue region-growing method that produces connected superpixels without iterative clustering.; SLIC — A local clustering method that groups pixels into approximately uniform superpixels using a distance over colour and image position.
↑ The measurement ―― −距離変換の地形の 1 本の最小全域森を、dynamics ≤ θ の辺で結ぶ。θ = 0 は雑音の凹みまで盆地にして過分割、θ = 0.5〜3 px で粒の数ちょうど、くびれの深さ(重なり 20 %)を超えると隣の粒と融合する。 (figure labels are in Japanese; the numbers are the same)
↑ エネルギーは毎回厳密に最小(最大フロー = 最小カット)。
↑ 結晶粒は q = 256 付近で VI が 1 bit を切る。
↑ Kittler は 2 クラスの分散を別々に持つので Bayes の最小誤差の閾値(2 次方程式の根)に乗る。
↑ 照明の勾配では背景の裾が長く、2 平均の中点(isodata)は背景の中に落ちる。
↑ The animation ―― 触れ合う粒の階層分水嶺。θ を大 → 小 → 大に振る: 1 領域から粒の数ちょうどを経て雑音の凹みで過分割へ、そして戻る。どのコマも同じ 1 本の木の切り方(入れ子)。
↑ The animation ―― 結晶粒の SRM。q = 1(全部 1 領域)から 1024(細切れ)へ。q = 256 前後で粒界に沿う。
↑ The animation ―― graph cut の λ を 0 → 0.3。黄 = 正解の物体、赤 = 余計、青 = 取り逃し。左の雑音の点は消え、右の細い線も同じ λ で途切れて消える。
py -3.11 examples/poc_graph_hierarchy_segmentation.py
Source: examples/poc_graph_hierarchy_segmentation.py
This run produced 10 figures in total - see them all
Ops used (notes): alpha_expansion · alpha_tree · area_opening_attr · graph_cut_binary · hierarchical_watershed · quasi_flat_zones · quickshift · seg_boundary_f · seg_dice_jaccard · seg_object_counts_match · seg_score_card · seg_under_over_segmentation · snic_superpixels · statistical_region_merging · superpixel_quality · text_box · threshold_isodata · threshold_kapur · threshold_kittler · threshold_triangle · ultrametric_contour_map · world_blobs_touching · world_gradient_illumination · world_grains_voronoi … (+3)
Tabanid flies cannot slow down in a controlled way before zebra stripes (Caro et al. 2019) and fly faster, keep away and do not land near stripes or checkerboards (How et al. 2020). This PoC counts how much of the folk claim that stripes spoil the sense of distance can be explained by the closed forms of a compound eye and a Hassenstein–Reichardt correlator. Six new ops: fly_view_plane_texture samples a periodic frontal surface analytically under the hexagonal lattice and a Gaussian acceptance function (no image resampling, 1e-9 against the closed form), sampling_alias_distance returns the distance P cos^2(theta)/(sqrt(3) dphi) beyond which a period P aliases on a lattice of pitch dphi (also the working-distance rule of an inspection camera), fly_expansion_readout reads tau from the flow over the whole eye, traj_tau_dot gives tau and tau-dot along a measured trajectory, fly_vogel_lattice is a Fibonacci (Vogel-spiral) eye and lattice_moire_peak the sharpness of the moire. A 7 cm stripe aliases for a tabanid eye (dphi 1.1 deg) beyond 2.105 m head-on and 0.526 m at 60 deg, while a Drosophila eye (4.63 / 8.23 deg) blurs it away (MTF 0.024). The HR sign reversal agrees with the closed form in more than 97 % of ommatidia, retreats to the periphery on approach, and the azimuthal pairs switch from ‘receding’ to ‘approaching’ at 1.71 m against a predicted 1.65 to 1.77 m. The aperture effect of vertical stripes returns exactly 2 tau in both the radial fit and the divergence (closed form 2, measured 1.992) and a checkerboard restores tau, which reproduces in numbers the logic of How et al. 2020 that avoidance of checkerboards rules out the aperture effect. The temporal frequency v tan(theta)/P during the approach does not depend on distance, so the correlator loses the range. The Fibonacci eye (same 721 ommatidia, same mean spacing) wins on moire diffusion (peak sharpness 2521 hexagonal / 4.3 Vogel / 5.2 jittered hexagonal), the hexagon resolves best just below Nyquist (residual 0.021 / 0.035 / 0.022), false flow is set by pair spacing rather than by lattice regularity (every azimuthal pair of the hexagon is reversed, every diagonal pair is correct), and a fovea (density power 0.5 to 0.8) sharpens the true-frequency peak from 5.2 to 11.0. The external truth (How et al. 2020’s Dryad 3-D trajectories, CC0) runs when placed and is skipped with an explicit ‘not placed’ otherwise. Dettlaff 2026 (arXiv:2605.17049, preprint, single author, code not public) is named as the moire hypothesis this is compared against. Nothing is learned; one gain is calibrated.
↑ A Fly’s Eye Closing In on Stripes — Do Zebra Stripes Really Spoil a Compound Eye’s Sense of Distance, and Would a Fibonacci Eye Do Better? ―― Tabanid flies cannot slow down in a controlled way before zebra stripes (Caro et al. 2019) and fly faster, keep away and do not land near stripes or checkerboards (How et al. 2020). This PoC counts how much of the folk claim that stripes spoil the sense of distance can be explained by the closed forms of a compound eye and a Hassenstein–Reichardt correlator. Six new ops: fly_view_plane_texture samples a periodic frontal surface analytically under the hexagonal lattice and a Gaussian acceptance function (no image resampling, 1e-9 against the closed form), sampling_alias_distance returns the distance P cos^2(theta)/(sqrt(3) dphi) beyond which a period P aliases on a lattice of pitch dphi (also the working-distance rule of an inspection camera), fly_expansion_readout reads tau from the flow over the whole eye, traj_tau_dot gives tau and tau-dot along a measured trajectory, fly_vogel_lattice is a Fibonacci (Vogel-spiral) eye and lattice_moire_peak the sharpness of the moire. A 7 cm stripe aliases for a tabanid eye (dphi 1.1 deg) beyond 2.105 m head-on and 0.526 m at 60 deg, while a Drosophila eye (4.63 / 8.23 deg) blurs it away (MTF 0.024). The HR sign reversal agrees with the closed form in more than 97 % of ommatidia, retreats to the periphery on approach, and the azimuthal pairs switch from ‘receding’ to ‘approaching’ at 1.71 m against a predicted 1.65 to 1.77 m. The aperture effect of vertical stripes returns exactly 2 tau in both the radial fit and the divergence (closed form 2, measured 1.992) and a checkerboard restores tau, which reproduces in numbers the logic of How et al. 2020 that avoidance of checkerboards rules out the aperture effect. The temporal frequency v tan(theta)/P during the approach does not depend on distance, so the correlator loses the range. The Fibonacci eye (same 721 ommatidia, same mean spacing) wins on moire diffusion (peak sharpness 2521 hexagonal / 4.3 Vogel / 5.2 jittered hexagonal), the hexagon resolves best just below Nyquist (residual 0.021 / 0.035 / 0.022), false flow is set by pair spacing rather than by lattice regularity (every azimuthal pair of the hexagon is reversed, every diagonal pair is correct), and a fovea (density power 0.5 to 0.8) sharpens the true-frequency peak from 5.2 to 11.0. The external truth (How et al. 2020’s Dryad 3-D trajectories, CC0) runs when placed and is skipped with an explicit ‘not placed’ otherwise. Dettlaff 2026 (arXiv:2605.17049, preprint, single author, code not public) is named as the moire hypothesis this is compared against. Nothing is learned; one gain is calibrated.
↑ The measurement ―― the alias distance P/(sqrt3 dphi) for a 7 cm period against the inter-ommatidial angle; the acceptance ratio changes only the contrast left at Nyquist, not the distance (figure labels are in Japanese; the numbers are the same)
↑ moire sharpness = strongest peak / median of the sub-Nyquist spectrum; false flow = energy of HR pair responses with the wrong sign / total, and the c…
↑ この回の図
↑ The animation ―― the time-to-contact read by the HR pathway (radial fit of the flow over the eye, one gain calibrated on the first checkerboard window) against the true tau during an approach at 0.5 m/s: over vertical stripes it reads longer than over a checkerboard (median ratio 1.52; the closed-form aperture effect says 2) while the checkerboard itself drifts from tau as the correlator’s phase moves with distance. The 1/f texture is left off the axes: with the same single gain it reads 14 to 28 tau, because its flow energy is a thousandth of the checkerboard’s — a correlator is not a speedometer
py -3.11 examples/poc_zebra_stripes_fly_eye.py
Source: examples/poc_zebra_stripes_fly_eye.py
This run produced 5 figures in total - see them all
Ops used (notes): coherence · fly_emd_response · fly_expansion_readout · fly_flow_from_directions · fly_hex_lattice · fly_t4t5_field · fly_tau_from_expansion · fly_view_plane_texture · fly_vogel_lattice · jitter · lattice_moire_peak · sampling_alias_distance · text_box · traj_tau_dot
Sim-to-real calibration (simcal) of pixel-shift offsets, pixel aperture and the lens Q = λN/p from the captured frames, refusing numbers that the data cannot fix. The centrepiece is an ‘undetermined map’ of a Siemens star cut into 8-pixel tiles: on the spokes the shift is fixed only along the tangent, the Cramér-Rao ellipses stretch radially, and the ‘determined combination’ simcal returns lies within 2.5 degrees of the closed-form tangent. This chapter found a bug in simcal (the determined combination was divided by the sensitivity column norms instead of multiplied), now fixed with a regression test. Injected ±0.1 px shifts are recovered at the Cramér-Rao bound (Fisher-distance chi-square p = 0.62); a noise model off by 4x fails the same gate (p = 3e-21). A rotated frame fitted by translation only is rejected by the residual PCA and the absolute chi-square gate. With half-pixel shifts the fitted aperture and Q give a resolution gain of 1.420 ± 0.005 (closed form 1.414), and Q = 2.5 is lens-limited with gain 1; a stuck row actuator and a 60 DN dim capture both make it refuse the gain. On real raw frames (PixelShift200, Qian et al., ICCP 2022, CC BY 4.0) the G-to-G diagonal pairs agree tile by tile with phase correlation (paired t-test p = 0.22 / 0.30) and the cross-colour pair is refused (chi-square per dof 31). No training.
↑ Calibrating Pixel-Shift Offsets and Pixel Aperture from the Images — Where Only Stripes Are Seen, the Shift Is Fixed in One Direction Only ―― Sim-to-real calibration (simcal) of pixel-shift offsets, pixel aperture and the lens Q = λN/p from the captured frames, refusing numbers that the data cannot fix. The centrepiece is an ‘undetermined map’ of a Siemens star cut into 8-pixel tiles: on the spokes the shift is fixed only along the tangent, the Cramér-Rao ellipses stretch radially, and the ‘determined combination’ simcal returns lies within 2.5 degrees of the closed-form tangent. This chapter found a bug in simcal (the determined combination was divided by the sensitivity column norms instead of multiplied), now fixed with a regression test. Injected ±0.1 px shifts are recovered at the Cramér-Rao bound (Fisher-distance chi-square p = 0.62); a noise model off by 4x fails the same gate (p = 3e-21). A rotated frame fitted by translation only is rejected by the residual PCA and the absolute chi-square gate. With half-pixel shifts the fitted aperture and Q give a resolution gain of 1.420 ± 0.005 (closed form 1.414), and Q = 2.5 is lens-limited with gain 1; a stuck row actuator and a 60 DN dim capture both make it refuse the gain. On real raw frames (PixelShift200, Qian et al., ICCP 2022, CC BY 4.0) the G-to-G diagonal pairs agree tile by tile with phase correlation (paired t-test p = 0.22 / 0.30) and the cross-colour pair is refused (chi-square per dof 31). No training.
↑ The measurement ―― 【1 章】同じ 4 つのつまみ(フレームのずれ dr・dc・回転・露出)を、縞の向きだけが違う 6 種の被写体で Morris のふるいにかけた。撮れた生の画像(32 × 32 画素を 8 倍、灰色は白の釣り合いを戻した値)と、dr・dc の分類。縦縞では縞に沿った dr が画像を変えず「効かない」、斜めの縞では dr と dc が縞を横切る組(右上がりは dr + dc、右下がりは dr − dc)でしか決まらない —— 開口問題。 (figure labels are in Japanese; the numbers are the same)
↑ 【2 章】3 枚のフレームに dr・dc ∈ ±0.1 px、回転 ±2 mrad、露出 ±2 % を注入し、物理の経路(ポアソン + 読み出し雑音 + 丸め)で撮って合わせた。
↑ 【4 章】3 mrad 回ったフレームを 2 つのモデルで合わせた残差(雑音の標準偏差で割り、5 × 5 で平均、8 倍。
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_simcal_pixel_shift/05_resolution_budget.png) |
| *↑ 【5 章】半画素シフト 4 枚(白黒)で、開口率・レンズの Q = λN/p・3 枚のずれを合わせ、pixel_shift_resolution_budget の閉形式( | sinc(f·開口率) | × 回折の MTF)に入れた。* |
↑ 【実データ】ピクセルシフト 4 枚の未加工 RAW の 1 枚目(白の釣り合いを掛けたモザイクのまま、256 × 1152 画素を 2 倍)。
py -3.11 examples/poc_simcal_pixel_shift.py
Source: examples/poc_simcal_pixel_shift.py
This run produced 6 figures in total - see them all
Ops used (notes): pixel_shift_capture · pixel_shift_merge · pixel_shift_pattern · pixel_shift_resolution_budget · simcal_discrepancy · simcal_fit · simcal_identifiability · simcal_model · simcal_observe · simcal_screen · simcal_simulate · simcal_validate · text_box
Water is a moving lens seen from above and a colour filter that thickens with distance seen from below. Ten new ops (watermedia, category watermedia of opsoptics): ocean_spectrum (S(omega) of Pierson-Moskowitz and JONSWAP, significant height and period), ocean_height_field (a Tessendorf FFT sea: height, slopes, normals, choppiness, depth), fresnel_water_air (the index of water is the real part of the material table with dispersion; flags total internal reflection), snell_window (window radius and apparent depth), refracted_view (where an underwater point appears, as a stationary point of the optical path: bisection on a flat surface, Newton iteration on a wavy one with the vector Snell law verified and failures returned as NaN), water_attenuation (Pope & Fry pure-water absorption, Smith & Baker scattering, Solonenko & Mobley a, b and Kd of the Jerlov types), underwater_image (Akkaynak & Treibitz image formation: direct signal plus backscatter), phase_function_hg / scattering_phase_petzold (the Petzold volume scattering function) and cylinder_lens_caustic (paraxial focus and real-ray caustic of a cylinder). (1) A Pierson-Moskowitz sea at 8 m/s: significant height 1.3656 m (0.21 U^2/g = 1.3705 m, 0.36 %), peak 1.0753 rad/s (0.877 g/U = 1.0751, 2e-4), JONSWAP with gamma = 1 equals PM to 1e-9, the height is real (imaginary part 3.5e-16), the spatial variance is within 0.02 % of the integral of the spectrum (8 seeds) and a single mode has the period 2 pi / omega to 2.8e-16. Against Cox & Munk (0.003 + 0.00512 U) the slope variance of the resolved waves alone is 11 % at 5 m/s and 16 % at 10 m/s (PM has no capillary waves); carrying the saturation range to the capillary wave number 370 /m gives 98 % (-2.5 %) at 5 m/s but 61 % (-38.7 %) at 10 m/s, kept in the gates as the limit that the constant alpha of PM does not grow with the wind. (2) A checkerboard on the bottom of a 2 m pool: the apparent depth straight above is d / n to 0, the found path satisfies Snell’s law to 7.8e-16, the flat refraction point agrees with the quartic to 1e-15 and with a direct minimisation of the optical path to 2.8e-8, and on a wavy surface the backward ray trace that builds the image returns to the same bottom point within 9.6e-9 m. In a fisheye looking up from under water the total internal reflection starts within one pixel (1.5 degrees) after the critical angle 48.59 degrees. (3) A water-filled bottle (R = 30 mm, n = 1.52, wall 1.5 mm, water 1.333): paraxial focus 61.24 mm, marginal ray 40.01 mm (spherical aberration 21.23 mm); the paraxial focus of a rod matches n R / (2 (n - 1)) and the real-ray focus matches the trigonometric chord to 3.6e-14. The power is 0.01633 /mm filled and -0.00120 /mm empty (ratio 13.6); on a screen 50 mm behind, the centre is 4.88 times the incident light where there is water (peak 13.97) and 0.94 times where the bottle is empty. (4) A colour chart at 1, 5 and 15 m: the Pope & Fry absorption has its minimum 0.00442 /m at 417.5 nm and 0.624 /m at 700 nm (table values), Beer-Lambert agrees with glassmirror to 1.1e-16, a count of 200,000 photons follows the exponential law to 0.2 sigma, Kd at 500 nm is 0.029 < 0.064 < 0.109 < 0.120 (I < II < III < 1C), red decays 12.2 times faster than blue in Jerlov I (transmission over 10 m: red 0.074, green 0.557, blue 0.807) and the R of the red patch at 15 m is 0.010. (5) Limits: the flat-surface apparent depth d / n is wrong the more obliquely one looks (7 % too deep at 30 degrees, 52 % at 60). The probes that break the answer on purpose (an index of 1.4, alpha changed by 10 %, Snell’s law reversed) turn a gate red. No learning, no calibration, 43 gates.
↑ Water and Transparent Media — Ocean Waves, Surface Refraction, Colour Under Water and the Caustic of a Bottle, Scored Against Closed Forms, Published Values and Second Implementations ―― Water is a moving lens seen from above and a colour filter that thickens with distance seen from below. Ten new ops (watermedia, category watermedia of opsoptics): ocean_spectrum (S(omega) of Pierson-Moskowitz and JONSWAP, significant height and period), ocean_height_field (a Tessendorf FFT sea: height, slopes, normals, choppiness, depth), fresnel_water_air (the index of water is the real part of the material table with dispersion; flags total internal reflection), snell_window (window radius and apparent depth), refracted_view (where an underwater point appears, as a stationary point of the optical path: bisection on a flat surface, Newton iteration on a wavy one with the vector Snell law verified and failures returned as NaN), water_attenuation (Pope & Fry pure-water absorption, Smith & Baker scattering, Solonenko & Mobley a, b and Kd of the Jerlov types), underwater_image (Akkaynak & Treibitz image formation: direct signal plus backscatter), phase_function_hg / scattering_phase_petzold (the Petzold volume scattering function) and cylinder_lens_caustic (paraxial focus and real-ray caustic of a cylinder). (1) A Pierson-Moskowitz sea at 8 m/s: significant height 1.3656 m (0.21 U^2/g = 1.3705 m, 0.36 %), peak 1.0753 rad/s (0.877 g/U = 1.0751, 2e-4), JONSWAP with gamma = 1 equals PM to 1e-9, the height is real (imaginary part 3.5e-16), the spatial variance is within 0.02 % of the integral of the spectrum (8 seeds) and a single mode has the period 2 pi / omega to 2.8e-16. Against Cox & Munk (0.003 + 0.00512 U) the slope variance of the resolved waves alone is 11 % at 5 m/s and 16 % at 10 m/s (PM has no capillary waves); carrying the saturation range to the capillary wave number 370 /m gives 98 % (-2.5 %) at 5 m/s but 61 % (-38.7 %) at 10 m/s, kept in the gates as the limit that the constant alpha of PM does not grow with the wind. (2) A checkerboard on the bottom of a 2 m pool: the apparent depth straight above is d / n to 0, the found path satisfies Snell’s law to 7.8e-16, the flat refraction point agrees with the quartic to 1e-15 and with a direct minimisation of the optical path to 2.8e-8, and on a wavy surface the backward ray trace that builds the image returns to the same bottom point within 9.6e-9 m. In a fisheye looking up from under water the total internal reflection starts within one pixel (1.5 degrees) after the critical angle 48.59 degrees. (3) A water-filled bottle (R = 30 mm, n = 1.52, wall 1.5 mm, water 1.333): paraxial focus 61.24 mm, marginal ray 40.01 mm (spherical aberration 21.23 mm); the paraxial focus of a rod matches n R / (2 (n - 1)) and the real-ray focus matches the trigonometric chord to 3.6e-14. The power is 0.01633 /mm filled and -0.00120 /mm empty (ratio 13.6); on a screen 50 mm behind, the centre is 4.88 times the incident light where there is water (peak 13.97) and 0.94 times where the bottle is empty. (4) A colour chart at 1, 5 and 15 m: the Pope & Fry absorption has its minimum 0.00442 /m at 417.5 nm and 0.624 /m at 700 nm (table values), Beer-Lambert agrees with glassmirror to 1.1e-16, a count of 200,000 photons follows the exponential law to 0.2 sigma, Kd at 500 nm is 0.029 < 0.064 < 0.109 < 0.120 (I < II < III < 1C), red decays 12.2 times faster than blue in Jerlov I (transmission over 10 m: red 0.074, green 0.557, blue 0.807) and the R of the red patch at 15 m is 0.010. (5) Limits: the flat-surface apparent depth d / n is wrong the more obliquely one looks (7 % too deep at 30 degrees, 52 % at 60). The probes that break the answer on purpose (an index of 1.4, alpha changed by 10 %, Snell’s law reversed) turn a gate red. No learning, no calibration, 43 gates.
↑ The measurement ―― Tessendorf の FFT。有義波高 1.37 m、周期 5.8 s。波は +x(右)へ進む。 (figure labels are in Japanese; the numbers are the same)
↑ JONSWAP はピークを γ 倍に尖らせる。
↑ 空の瓶はほとんど曲げない(屈折力は水入りの 1/14)。
↑ 上: 外洋(ジャーロフ I)、下: 沿岸(3C)。
↑ 純水の吸収の最小は 417.5 nm の 0.00442 /m、700 nm で 0.624 /m。
↑ The animation ―― 同じFFT海面の時刻を進めると波面が風下へ動く。
py -3.11 examples/poc_water_transparent_media.py
Source: examples/poc_water_transparent_media.py
This run produced 20 figures in total - see them all
Ops used (notes): beer_lambert_transmittance · cylinder_lens_caustic · fresnel_water_air · ocean_height_field · ocean_spectrum · phase_function_hg · refract_rays · refracted_view · scattering_phase_petzold · snell_window · underwater_image · water_attenuation
Water changes what an inspection camera records in four ways: a wavy surface lays a bright web of caustics over the pattern on the floor, a bottle with liquid is a cylindrical lens, a wet surface is darker and glossier, and in murky water a lamp beside the lens makes a veil of backscatter. Seven new ops (watervision, category watervision of opsoptics): caustic_irradiance (the irradiance on a floor by a photon count and by the Jacobian of the surface-to-floor map), bottle_image (a side view of a bottle ray-cast in the horizontal section of every row, backlit or frontlit, with bubbles and a meniscus), fill_level_from_image (the fill line read back from the image), wet_surface_appearance (the Lekner and Dorf wet surface), wet_dry_inspection_pair (one plate rendered dry and wet, with the defect contrast), underwater_inspection_image (a hull seen with a lamp at the camera, and the per-pixel SNR) and caustic_floor_segmentation (the caustic lines as a mask). (1) Pool floor: a flat surface gives a uniform cos(theta) T to 1e-9, the photon total equals the incident beam times the mean transmittance to 1e-6 (periodic wrap), the photon count and the Jacobian agree to 0.30 % relative RMS on a smooth surface, at the folds (66 pixels on a surface of 0.2 m waves) the wavefront method returns inf and a flag while the photons count a finite value 5.0 times the mean, and doubling the ray density changes the result by 0.85 %. The crack detector (the registry’s canny) fires on the edges of the caustics at 3 m/s and its precision falls to 0.05 (1.00 on a plate without them); removing the edges with the caustic segmentation brings it back to 1.00 (recall 1.00 to 0.45). At 6 m/s the relative threshold loses to the gradient of the caustic edges (recall 0.15), masking does not bring the crack back (0.00), and dividing by the irradiance does (1.00 / 1.00). (2) Bottle (R = 30 mm, n = 1.52, water 1.333): rows above and below the line are bit identical to the empty and the full section, the central brightness ratio 1.0816 matches the ratio of the Fresnel transmittance products of the four interfaces to 1e-9, the half width of the lit band of the filled section is 9.50 mm (9.91 mm predicted from the paraxial focus of 62.13 mm, -4.1 %), the fill line is within 1 pixel for five fills and two lights (difference 0), and the outer diameter of the dark ring of a bubble is 54 pixels (53.3 from the paraxial 2 x 2 matrix, magnification 1.333). (3) Wet surface: a film of 0 leaves the albedo unchanged, the closed form agrees with an independent quadrature to 1.1e-16, wet over dry is 0.673 at a = 0.5, the diffuse contrast of a scratch goes from 0.411 to 0.494 (0.494 predicted), the total from 0.449 to 0.558, and the glint pixels from 361 to 127. (4) Hull (1, 3 and 6 m, Jerlov I and 3C): the SNR falls monotonically with distance, pure water as the distance tends to 0 equals the clear-water case to 2.7e-11, and the veil in the centre is about 5 times that in the corner. (5) Limit: the bottle is traced section by section and ignores the vertical tilt of the rays; at a tilt of 40 degrees the half width of the band moves by 29 % (a vertical cylinder is exact with effective indices, a shoulder is not covered). No learning, no calibration, 39 gates.
↑ How Water Looks in Inspection Images — Pool-Floor Caustics, a Bottle from the Side, a Wet Plate and a Hull Under Water, Scored Against Closed Forms and Second Methods ―― Water changes what an inspection camera records in four ways: a wavy surface lays a bright web of caustics over the pattern on the floor, a bottle with liquid is a cylindrical lens, a wet surface is darker and glossier, and in murky water a lamp beside the lens makes a veil of backscatter. Seven new ops (watervision, category watervision of opsoptics): caustic_irradiance (the irradiance on a floor by a photon count and by the Jacobian of the surface-to-floor map), bottle_image (a side view of a bottle ray-cast in the horizontal section of every row, backlit or frontlit, with bubbles and a meniscus), fill_level_from_image (the fill line read back from the image), wet_surface_appearance (the Lekner and Dorf wet surface), wet_dry_inspection_pair (one plate rendered dry and wet, with the defect contrast), underwater_inspection_image (a hull seen with a lamp at the camera, and the per-pixel SNR) and caustic_floor_segmentation (the caustic lines as a mask). (1) Pool floor: a flat surface gives a uniform cos(theta) T to 1e-9, the photon total equals the incident beam times the mean transmittance to 1e-6 (periodic wrap), the photon count and the Jacobian agree to 0.30 % relative RMS on a smooth surface, at the folds (66 pixels on a surface of 0.2 m waves) the wavefront method returns inf and a flag while the photons count a finite value 5.0 times the mean, and doubling the ray density changes the result by 0.85 %. The crack detector (the registry’s canny) fires on the edges of the caustics at 3 m/s and its precision falls to 0.05 (1.00 on a plate without them); removing the edges with the caustic segmentation brings it back to 1.00 (recall 1.00 to 0.45). At 6 m/s the relative threshold loses to the gradient of the caustic edges (recall 0.15), masking does not bring the crack back (0.00), and dividing by the irradiance does (1.00 / 1.00). (2) Bottle (R = 30 mm, n = 1.52, water 1.333): rows above and below the line are bit identical to the empty and the full section, the central brightness ratio 1.0816 matches the ratio of the Fresnel transmittance products of the four interfaces to 1e-9, the half width of the lit band of the filled section is 9.50 mm (9.91 mm predicted from the paraxial focus of 62.13 mm, -4.1 %), the fill line is within 1 pixel for five fills and two lights (difference 0), and the outer diameter of the dark ring of a bubble is 54 pixels (53.3 from the paraxial 2 x 2 matrix, magnification 1.333). (3) Wet surface: a film of 0 leaves the albedo unchanged, the closed form agrees with an independent quadrature to 1.1e-16, wet over dry is 0.673 at a = 0.5, the diffuse contrast of a scratch goes from 0.411 to 0.494 (0.494 predicted), the total from 0.449 to 0.558, and the glint pixels from 361 to 127. (4) Hull (1, 3 and 6 m, Jerlov I and 3C): the SNR falls monotonically with distance, pure water as the distance tends to 0 equals the clear-water case to 2.7e-11, and the veil in the centre is about 5 times that in the corner. (5) Limit: the bottle is traced section by section and ignores the vertical tilt of the rays; at a tilt of 40 degrees the half width of the band moves by 29 % (a vertical cylinder is exact with effective indices, a shoulder is not covered). No learning, no calibration, 39 gates.
↑ The measurement ―― 照度の最大 3.0 倍(3 m/s)・7.7 倍(6 m/s)。分割は床の 29 %・21 %。傾きの RMS は手で 0.06・0.10 に合わせた。 (figure labels are in Japanese; the numbers are the same)
↑ 上: 3 m/s は分割で縁を除くと適合率が戻る。
↑ 空の瓶は縁近くまで明るい。
↑ 全体のコントラスト 0.449 → 0.558。
↑ 内部反射 r = 0.476 の水–空気界面が光を膜に閉じ込め、基板に吸わせる。
↑ The animation ―― 照明角を動かすと濡れた板のてかりが移動する。
py -3.11 examples/poc_water_inspection_images.py
Source: examples/poc_water_inspection_images.py
This run produced 16 figures in total - see them all
Ops used (notes): bottle_image · canny · caustic_floor_segmentation · caustic_irradiance · fill_level_from_image · fresnel_water_air · ocean_height_field · otsu · underwater_inspection_image · water_attenuation · wet_dry_inspection_pair · wet_surface_appearance
A lens-design screen reports spot diagrams and MTF (modulation transfer function) numbers but not what the prescription records when it photographs a document one metre away. The exit pupil is sampled from the sensor, every ray is traced backwards through the whole prescription (Kolb, Mitchell & Hanrahan 1995), the scene is shaded in object space, and photons, shot noise, read noise and quantisation follow. Eight new ops (lensrender, category lensrender of opsoptics): lens_scene (validated planes, spheres, meshes and lights), exit_pupil_samples, trace_sensor_rays (sensor, pupil, every surface backwards; vignetted and totally reflected rays are NaN and flagged), scene_radiance (Lambertian plus Blinn-Phong plus shadows), render_scene_through_lens (irradiance, electrons, noise, image), bokeh_shape (the cat’s eye), render_ghost_overlay (two-reflection ghosts) and render_compare_prescriptions (a quality table per prescription). (1) The backward trace is the reverse of ray_bundle (entrance point 8.7e-15 mm, direction 4.2e-16); a white plane through an ideal lens gives pi L sin^2(alpha) to 2e-6 relative, a point source lands on one pixel with the photon count I Omega t QE lambda/hc to 1e-6, the 15 checkerboard edges follow distortion_map to 0.096 px (the distortion is 5.2 px at the outer edge) and the lateral colour at 0.7 field follows chromatic_shift to 0.08 px. (2) Two triplets of the same EFL and F-number (one with its radii detuned by 1.5 times 7 to 10 %) photograph a resolution chart at 1 m: the on-axis RMS spot is 10.7 and 53.4 um (5.0 times), the 10-90 % edge rise 4.6 and 15.4 px, MTF50 0.116 and 0.053 cyc/px, in the order of spot_stats. (3) The bokeh of a point at 0.5 m through an ideal lens is the thin-lens circle of confusion to 0.31 %, and F/8 is 0.349 times F/2.8 (2.8/8 = 0.350). With a round baffle 20 mm behind an ideal lens the cat’s-eye area ratio agrees with the closed form for the overlap of two discs, 0.635, to 1.6 % (hull 0.625, rendered 0.639, pupil pass 0.636) and is symmetric about the meridional plane to 3.6e-15 mm. At the corner of the triplet field curvature makes the blur disc itself 1.3 times larger, so its area ratio does not equal the pupil pass (stated). (4) The relative illumination of a white plane follows lensimage to 1.61 % up to 0.7 field and differs by 3.2 % at 8.7 degrees (object-side against image-side cos^4); a singlet with the stop in front gives a -6.6 % barrel grid. (5) The energy ratios of 15 ghosts equal the closed form r^2 (1-r)^(2(j-i)-2) to 4.4e-16, the centroids agree with ghost_analysis to 8e-4 px and a reflectance of 0 gives an overlay of exactly 0. (6) The contrast of a 0.2 mm scratch (10.4 um on the sensor) is 0.547 through the good lens and 0.261 (48 %) through the detuned one, against 0.542 and 0.261 from an independent prediction (forward-traced spot convolved with the line). (7) Honest limit: geometric optics has no diffraction, so the point image of the good lens stopped down to F/11 is drawn at 0.32 times the Airy disc (at F/6.25, aberration-limited, it is 2.7 times). (8) Break-it probes (reversed index ratio, aperture radius off by 10 %, focus moved to 1.5 m) turn the gates red. Learning 0, calibration 0, 30 gates.
↑ Photographing a 3-D Scene Through the Actual Lens Prescription — Depth of Field, Cat’s-Eye Bokeh, Corner Illumination, Colour Fringes and Ghosts, Scored Against Closed Forms, Existing Ops and an Independent Trace ―― A lens-design screen reports spot diagrams and MTF (modulation transfer function) numbers but not what the prescription records when it photographs a document one metre away. The exit pupil is sampled from the sensor, every ray is traced backwards through the whole prescription (Kolb, Mitchell & Hanrahan 1995), the scene is shaded in object space, and photons, shot noise, read noise and quantisation follow. Eight new ops (lensrender, category lensrender of opsoptics): lens_scene (validated planes, spheres, meshes and lights), exit_pupil_samples, trace_sensor_rays (sensor, pupil, every surface backwards; vignetted and totally reflected rays are NaN and flagged), scene_radiance (Lambertian plus Blinn-Phong plus shadows), render_scene_through_lens (irradiance, electrons, noise, image), bokeh_shape (the cat’s eye), render_ghost_overlay (two-reflection ghosts) and render_compare_prescriptions (a quality table per prescription). (1) The backward trace is the reverse of ray_bundle (entrance point 8.7e-15 mm, direction 4.2e-16); a white plane through an ideal lens gives pi L sin^2(alpha) to 2e-6 relative, a point source lands on one pixel with the photon count I Omega t QE lambda/hc to 1e-6, the 15 checkerboard edges follow distortion_map to 0.096 px (the distortion is 5.2 px at the outer edge) and the lateral colour at 0.7 field follows chromatic_shift to 0.08 px. (2) Two triplets of the same EFL and F-number (one with its radii detuned by 1.5 times 7 to 10 %) photograph a resolution chart at 1 m: the on-axis RMS spot is 10.7 and 53.4 um (5.0 times), the 10-90 % edge rise 4.6 and 15.4 px, MTF50 0.116 and 0.053 cyc/px, in the order of spot_stats. (3) The bokeh of a point at 0.5 m through an ideal lens is the thin-lens circle of confusion to 0.31 %, and F/8 is 0.349 times F/2.8 (2.8/8 = 0.350). With a round baffle 20 mm behind an ideal lens the cat’s-eye area ratio agrees with the closed form for the overlap of two discs, 0.635, to 1.6 % (hull 0.625, rendered 0.639, pupil pass 0.636) and is symmetric about the meridional plane to 3.6e-15 mm. At the corner of the triplet field curvature makes the blur disc itself 1.3 times larger, so its area ratio does not equal the pupil pass (stated). (4) The relative illumination of a white plane follows lensimage to 1.61 % up to 0.7 field and differs by 3.2 % at 8.7 degrees (object-side against image-side cos^4); a singlet with the stop in front gives a -6.6 % barrel grid. (5) The energy ratios of 15 ghosts equal the closed form r^2 (1-r)^(2(j-i)-2) to 4.4e-16, the centroids agree with ghost_analysis to 8e-4 px and a reflectance of 0 gives an overlay of exactly 0. (6) The contrast of a 0.2 mm scratch (10.4 um on the sensor) is 0.547 through the good lens and 0.261 (48 %) through the detuned one, against 0.542 and 0.261 from an independent prediction (forward-traced spot convolved with the line). (7) Honest limit: geometric optics has no diffraction, so the point image of the good lens stopped down to F/11 is drawn at 0.32 times the Airy disc (at F/6.25, aberration-limited, it is 2.7 times). (8) Break-it probes (reversed index ratio, aperture radius off by 10 %, focus moved to 1.5 m) turn the gates red. Learning 0, calibration 0, 30 gates.
Terms: MTF — The magnitude of the optical transfer function, showing how image contrast is transferred at each spatial frequency.
↑ The measurement ―― 同じチャートを良好な処方からずらした処方へ連続的に比較する。 (figure labels are in Japanese; the numbers are the same)
↑ RMS スポットは raytrace.spot_stats、光量は lensimage、刃は edgesfr で同じ絵から測った。
↑ 遮光筒が隅の斜めの光束を削り、瞳の通過率は 90 %(形は円から外れる)。
↑ 絞りを前に置いた単玉(樽型 -6.6 %)で撮った 30 mm 格子の絵。
↑ 良い三枚玉では傷の中心の画素が 55 % 暗くなるが、ずらした方では 26 % しか暗くならず、周りの画素と区別がつきにくい。
py -3.11 examples/poc_photograph_through_prescription.py
Source: examples/poc_photograph_through_prescription.py
This run produced 22 figures in total - see them all
Ops used (notes): bokeh_shape · chief_ray · chromatic_shift · depth_of_field · distortion_map · exit_pupil_samples · ghost_analysis · lens_scene · lens_system · noncoaxial_system · paraxial_trace · ray_bundle · relative_illumination · render_compare_prescriptions · render_ghost_overlay · render_scene_through_lens · spot_stats · trace_sensor_rays · with_wavelength
Treat a 2-D video as one (t, y, x) volume and the 3-D ops — connected components, isosurfaces, region properties — work along time unchanged. Merging colonies become a Y in space-time, passing vehicles become bands in a (t, x) image, a wavefront’s arrival time becomes an isosurface. The 21 exhibits here demonstrate exactly that.
The time axis also brings its own traps. Rounding onto the frame grid always delays; pixel area makes merging look early. Mislinks come in two opposite kinds, so a single error rate cannot say which way the diffusion coefficient is wrong. Template tracking drifts quietly before it ever loses the target, and all 152 drifted frames report ‘found’.
The motion-magnification exhibit carries the most candid conclusion in the room: exact to machine precision up to a magnification of 200, and of no help for measurement. Magnification is a tool for showing people, not for measuring.
A correct vibration frequency in video does not ensure correct damping. Noise and lighting flicker were added to a freely decaying cantilever; modal frequencies stayed within 0.06 Hz. At low amplitude, damping fails before frequency and varies with the estimator used on the same time series.
↑ Modal identification from video — frequency survives to the end, damping lies first ―― A cantilever beam (closed-form Euler–Bernoulli modes) decaying freely in three modes is rendered into video with sensor noise, 100 Hz lighting flicker, camera shake and rolling shutter, then identified with phase-based displacement (phase_displacement) and PIV (piv_cross_correlate). All three natural frequencies come out within 0.06 Hz, but the damping ratio of mode 1 from the very same series is 0.0778 (half-power), 0.0188 (envelope) or 0.0181 (fit) against a true 0.02. Sweeping the amplitude from 0.02 to 2 px, the quantities break in the order f → ζ → MAC_2 → MAC_3: at 0.02 px the phase method still has f_1 within +0.030 Hz while ζ_1 is 0.23 of the truth. At 48.5 fps the flicker alias lands exactly on f_1 = 3.00 Hz; a single-pixel brightness zero-point cannot report ζ at all, while the phase method survives at 0.0191.
Terms: PIV — A flow-measurement method that estimates a velocity field from the displacement of particle patterns between images separated by a short time.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ この回の図
↑ この回の図
↑ The animation ―― 動画(640 × 502、24 fps、292 コマ): 片持ち梁の自由減衰(A_1 = 0.2 px、雑音・照明ちらつき・手ぶれ・ローリングシャッター入り、撮影 128 fps を 5.3 倍のスローで再生)。画面のままでは梁は動いて見えないので、12 測点のたわみを 50 倍に誇張して重ねた(白 = 真値、青 = 位相法の測定)。下段は先端の変位が時刻とともに伸びる(白 = 真値、青 = 位相法、朱 = PIV)。最後のコマが同定結果: f_1 は 3.013 Hz(真 3.000)と当たるが、同じ時系列から出した ζ_1 は 半値幅 0.0778 / 包絡線 0.0188 / 当てはめ 0.0181(真 0.020)と方法で 3 通りに割れる。
py -3.11 examples/poc_beam_modal_video.py
Source: examples/poc_beam_modal_video.py
This run produced 14 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · data_to_pixel · envelope · phase_displacement · piv_cross_correlate · temporal_bandpass · text_box · ticks
The question is whether one temperature record represents every product in a cold load. Product loggers were moved through a field with wall heat and door openings; 82.4 % of placements missed failing goods. Wall heat creates false passes, whereas short door pulses warm air but not product and reverse the error.
↑ The Cold-Chain Temperature Record — Where You Taped the Logger Is the Verdict ―― Twelve hours of a 12.0 m x 2.4 m reefer hold, built as a single (t, y, x) volume of 720 min x 60 x 12 cells with known closed-form physics: distance from the supply vent, infiltration from each of the four walls, five door-opening pulses, and first-order thermal lag (5 min for air, 64-91 min for product). 108 of 490 product cells (22.0 %) truly fail, yet a single logger taped in the product reports PASS for 82.4 % of the placements. Turning the causes off one at a time separates the failure modes: walls alone give 95.5 % false passes and 0.0 % false alarms, while walls off and doors on drop true failures to 2.0 % but raise false alarms to 3.5 % — and a logger in the product then passes 100 % of the time, because short pulses never enter the product. The cliffs can be predicted on paper: the time-constant cliff measured at 19-138 min matches a two-stage (hold air + logger) model within 18 % relative, and the sampling-interval and 0.5 K quantisation cliffs match their predictions exactly. Converted to “minutes allowed at 12 °C”, the three metrics computed from the same record differ by 4.6x (60 / 276 / 197 min) and disagree on the verdict at 82 of 720 placements (11.4 %).
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ 扉前の空気は跳ねるが製品には届かない。
↑ この回の図
↑ この回の図
↑ 1 枚目は render_volume_projection を幅方向から掛けた xray 投影(明るいほど幅方向に厚い)。
py -3.11 examples/poc_cold_chain_excursion.py
Source: examples/poc_cold_chain_excursion.py
This run produced 16 figures in total - see them all
Ops used (notes): esdf · integrate_funct_1d · quantize · render_volume_projection · sample_funct_1d · vol_label · vol_label_shape_stats · vol_mip · vol_profile_line · vol_region_props
A wall at 1 px = 0.15 mm, rephotographed for twelve epochs over three years, with a crack opening at a true 0.040 mm/yr. Thresholding and counting pixels understates the width by 25.0 % yet overstates the growth rate by +153.0 %, and invents +0.0117 mm/yr of growth in a control where the width is frozen (the culprit is blur, isolated by switching one nuisance at a time). Integrating the intensity deficit gives +3.1 % on the rate and +0.0005 mm/yr on the control. The thresholded rate swings from 0.0241 to 0.1038 mm/yr on initial width alone — it depends on whether a one-pixel step happens to fall inside the observation window.
↑ Measuring the Growth, Not the Width — Rephotographing the Same Wall Changes What the Error Is ―― A wall at 1 px = 0.15 mm, rephotographed for twelve epochs over three years, with a crack opening at a true 0.040 mm/yr. Thresholding and counting pixels understates the width by 25.0 % yet overstates the growth rate by +153.0 %, and invents +0.0117 mm/yr of growth in a control where the width is frozen (the culprit is blur, isolated by switching one nuisance at a time). Integrating the intensity deficit gives +3.1 % on the rate and +0.0005 mm/yr on the control. The thresholded rate swings from 0.0241 to 0.1038 mm/yr on initial width alone — it depends on whether a one-pixel step happens to fall inside the observation window.
↑ The measurement ―― 2 値化は幅の偏りより期ごとの跳ねが問題。跳ねの正体はぼけと画素位相で、4 節と 3 節で分けて数える。 (figure labels are in Japanese; the numbers are the same)
↑ ここに出る傾きはすべて『見かけの成長』。
↑ 傾きが 0 に近いほど列方向の画素位相が揃い、2 値化は画面ごと 1 画素単位で跳ぶ。
↑ 横線を下回った時点で 0.040 mm/年 を 2σ で言える。
↑ 2 値化は臨界幅より細いと 0(未検出)。
↑ The animation ―― 動画(576 × 456、12 fps、378 コマ): 同じ壁を 3 年 12 期撮り返す。各期で中心線に直交する断面を 24 本、左から順に切り(橙 = いま切っている断面、右下が拡大)、下段左がその断面の輝度欠損 —— その面積が幅そのもの。24 本の平均が積分法の幅(青、測りかけの期は橙の輪で途中平均)、朱のマスクの画素を数えたのが 2 値化(朱)。真の幅は 1 期 0.010 mm ずつ伸びる(0.067 画素)。最後の当てはめで成長率は 真値 0.0400 / 積分法 0.0412 / 2 値化 0.1012 mm/年。2 値化は期ごとに跳ね(0.0185〜0.3386 mm)、跳ねの正体はぼけ(PSF σ 0.75〜1.24 px)と据え直しの画素位相。
py -3.11 examples/poc_crack_width_timeseries.py
Source: examples/poc_crack_width_timeseries.py
This run produced 11 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · data_to_pixel · plot_series · text_box · ticks
A vibration of known amplitude 0.02 px at 3.7 Hz, synthesised to test whether motion magnification helps measurement. Exact to machine precision up to α = 200. But magnification does not improve measurement precision — multiplying the phase by α multiplies the noise by α. On a cantilever, phase correlation returns 0.15 px, the area average of 0.30 and 0.00 px, a number that exists nowhere.
↑ Micro-Vibration of a Structure From Video — Does Motion Magnification Help You Measure? ―― A vibration of known amplitude 0.02 px at 3.7 Hz, synthesised to test whether motion magnification helps measurement. Exact to machine precision up to α = 200. But magnification does not improve measurement precision — multiplying the phase by α multiplies the noise by α. On a cantilever, phase correlation returns 0.15 px, the area average of 0.30 and 0.00 px, a number that exists nowhere.
↑ The measurement ―― 剛体を仮定する位相相関が返す 0.150 px は 0.30 と 0.00 の面積平均で、どの列の真値とも違う。 (figure labels are in Japanese; the numbers are the same)
↑ 3.7 Hz を含む帯だけが 0 dB。
↑ 3.05 px までは機械精度、3.10 px で崩壊。
↑ The animation ―― 動画(480 × 480、12 fps、124 コマ): 3.7 Hz・0.1 px で揺れる表面(雑音 σ 0.01)。左が生の映像、右が 10 倍に拡大した映像で、朱の細い縦線は静止時の縞の山。生では揺れが表示 0.3 px で目に見えず、拡大後は 1 px 相当で見える。下段は変位の時系列: 白 = 真値、青 = 生の映像から測った値、橙 = 拡大後に測って 10 で割った値。振幅は 真 0.1000 / 生から 0.10012 / 拡大後 0.10013 px で、拡大しても測定は良くならない(見せるための道具)。
py -3.11 examples/poc_motion_magnification.py
Source: examples/poc_motion_magnification.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · band_snr · data_to_pixel · displacement_series · motion_magnify · phase_displacement · synthesize_translation · temporal_band_power · temporal_bandpass · text_box · ticks
A video of 400 particles tracked, with the diffusion coefficient D read from the trajectories. Ambiguous mislinks pull D down to 0.925x; mislinks caused by missing targets push it up to 3.429x in the same video — one error rate cannot tell you the direction. What helps is not a one-to-one constraint but a single line imposing a maximum link distance (3.429 → 1.304).
↑ Particle Tracking as a (row, column, time) Volume — Mislinks Come in Two Directions ―― A video of 400 particles tracked, with the diffusion coefficient D read from the trajectories. Ambiguous mislinks pull D down to 0.925x; mislinks caused by missing targets push it up to 3.429x in the same video — one error rate cannot tell you the direction. What helps is not a one-to-one constraint but a single line imposing a maximum link distance (3.429 → 1.304).
↑ The measurement ―― 時間最大投影では粒子が尾を引く(= 軌跡)。kymograph は行 90-101 の帯を縦(時間)へ積んだもので、筋の傾きがそのまま列方向の速度。縦は 5 倍に拡大。 (figure labels are in Japanese; the numbers are the same)
↑ 縦軸は常用対数(0 が真値)。
↑ 真値で割った比。
↑ 曖昧と欠測を分けて数えると、D の外れる向きが説明できる。
py -3.11 examples/poc_particle_tracking.py
Source: examples/poc_particle_tracking.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · blob_features · blob_label · plot_series · text_box · ticks · vol_label · vol_local_maxima
A 24 x 16 m road surface settling above a tunnel drive, measured from two epochs of point cloud. The nearest-neighbour baseline (C2C) returns a median of 47.74 mm even with zero change — that is just the point spacing — and carries no sign. M3C2 averages -2.43 mm, matching the truth, but only 275 of 551 cores (49.9 %) exceed their level of detection, and those average -4.34 mm: the single averaged number agrees with neither. LoD is a map of the surface, not of the settlement, ranging 0.51 to 3.58 mm across zones. Summing only the significant cores shrinks the volume from 0.8569 to 0.7643 m3, and that shortfall is predictable in advance from the per-core LoD (0.867 predicted versus 0.892 measured).
↑ Did It Settle, or Did We Just Scan It Again — What Changes When You Cut at the Detection Limit ―― A 24 x 16 m road surface settling above a tunnel drive, measured from two epochs of point cloud. The nearest-neighbour baseline (C2C) returns a median of 47.74 mm even with zero change — that is just the point spacing — and carries no sign. M3C2 averages -2.43 mm, matching the truth, but only 275 of 551 cores (49.9 %) exceed their level of detection, and those average -4.34 mm: the single averaged number agrees with neither. LoD is a map of the surface, not of the settlement, ranging 0.51 to 3.58 mm across zones. Summing only the significant cores shrinks the volume from 0.8569 to 0.7643 m3, and that shortfall is predictable in advance from the per-core LoD (0.867 predicted versus 0.892 measured).
↑ The measurement ―― 有意の地図は真値の地図をよく復元する(TPR 88.7 % / FPR 3.6 %)。ただし縁が痩せる —— そこが 7 節の体積の話。 (figure labels are in Japanese; the numbers are the same)
↑ 上の帯(砂利の路肩)は粗さ 15 mm・密度半分なので LoD が跳ね上がる。
↑ 予測は 1.96·σ·sqrt(1/na+1/nb)。
↑ LoD は雑音しか見ていないので、系統誤差はそのまま「有意な沈下」として通る。
↑ 崖の位置はそのゾーンの LoD で決まる。
py -3.11 examples/poc_settlement_significance.py
Source: examples/poc_settlement_significance.py
This run produced 7 figures in total - see them all
A camera moved by a known similarity transform, exposing the three ways template tracking fails: losing the target, drifting quietly, and being confidently wrong. All 152 of the 152 drifted frames passed the peak threshold calibrated without occlusion and reported ‘found’. The only absolute quantity measurable without ground truth is forward-backward inconsistency.
↑ Template Tracking Drifts Quietly Before It Ever Loses the Target ―― A camera moved by a known similarity transform, exposing the three ways template tracking fails: losing the target, drifting quietly, and being confidently wrong. All 152 of the 152 drifted frames passed the peak threshold calibrated without occlusion and reported ‘found’. The only absolute quantity measurable without ground truth is forward-backward inconsistency.
↑ The measurement ―― 同じ遮蔽率でも、そっくりな別物体が視野に居るだけで崖がはるかに手前へ来る。 (figure labels are in Japanese; the numbers are the same)
↑ 更新なしは平らなまま。
↑ 得意な崖が逆。
↑ The animation ―― 動画(30 フレーム + 最後で 2 秒止め、5 fps): 同じ 1 枚目のテンプレートを更新なし全域探索で追う(ゼロ点)。3 フレーム目から真の対象の 70 % を左から隠す。左 = 平坦な遮蔽物: ピークは平均 0.745 まで下がってしきい値 0.843 を割る(「見失った?」と正直に言う)が、位置は平均 0.78 px で追えている(見失い 0 / 27)。右 = そっくりな別物体が 51 px 離れて一緒に流れる: 最初の遮蔽フレームで複製に乗り換え、平均誤差 46.13 px、見失い 27 / 27 —— それなのにピークは平均 0.856 でしきい値を超え、27 / 27 フレームで「見つけた」と報告する。下段の紫(突出度)は右で平均 0.111 としきい値 0.304 を割って取り違えを疑うが、追えている左でも平均 0.203 で同じく割る —— 突出度が測っているのは曖昧さで、正しさではない
py -3.11 examples/poc_template_tracking.py
Source: examples/poc_template_tracking.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · ncc_locate · plot_series · reflect · shape_locate · text_box · ticks
A video of colonies spreading and merging, read as a (t, y, x) volume with 3-D connected components. Pixel area makes merging look early (-1.16 frames for pair 0-1) while rounding onto the frame grid makes it look late (+0.94); the two oppose, so the sum looks small. The default 26-connectivity of vol_label merged a near miss with a gap of 0.92.
↑ A Growth Time-Lapse as Space-Time Connected Components ―― A video of colonies spreading and merging, read as a (t, y, x) volume with 3-D connected components. Pixel area makes merging look early (-1.16 frames for pair 0-1) while rounding onto the frame grid makes it look late (+0.94); the two oppose, so the sum looks small. The default 26-connectivity of vol_label merged a near miss with a gap of 0.92.
↑ The measurement ―― 縦が時間(下向き、4 倍に拡大)、横が列。2 本の管が合わさる高さがそのまま合体時刻。色は 3-D ラベル。 (figure labels are in Japanese; the numbers are the same)
↑ 横軸はどちらも『何倍粗くしたか』。
↑ 空間側は格子の位相でこれだけ動く(偏りより大きい)。
↑ The animation ―― 動画(48 フレーム): 左は二値のフレームを 3-D の家族ラベルで塗ったもの(白の細線 = 真の連続円。色は体積全体で決まる家族なので、合体する 2 個は合体の前から同じ色)。右は合体する 2 組の中心を通る行の時空間断面が時刻とともに現れ、Y 字の分かれ目が合体時刻になる(白の点線 = 閉形式の真値 7.99 / 30.09、橙 = 観測 7 / 31)。下はフレームを独立に数えた塊の数で、減ったのは t = 7, 31, 47。最後の t = 47 の減少はニアミス 4-5(最終フレームでも隙間 0.92)を 8 近傍が繋いだ偽の合体で、真の個数は 5 のまま。
py -3.11 examples/poc_timelapse_growth.py
Source: examples/poc_timelapse_growth.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · blob_label · plot_series · text_box · ticks · vol_label · vol_region_props
A synthetic traffic video counted per frame, by a virtual loop, and by connected components in a (t, x) slit image. The per-frame maximum reports 7 for 10 vehicles passed — it measures a different quantity. All three failure conditions are written with one constant, vehicle length over speed = L/V (9.0 frames); at a frame interval of 16 the bands fragment and 10 vehicles become 49.
↑ Counting in (x, y, t) — Vehicles Passed, Occlusion, and One Constant: L/V ―― A synthetic traffic video counted per frame, by a virtual loop, and by connected components in a (t, x) slit image. The per-frame maximum reports 7 for 10 vehicles passed — it measures a different quantity. All three failure conditions are written with one constant, vehicle length over speed = L/V (9.0 frames); at a frame interval of 16 the bands fragment and 10 vehicles become 49.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ トラックは画像の 50 行から 99 行を占めるので、遠い車線の計数行 63 を横切る。
↑ 全部の帯を数えると千切れて過大に(実測は最大 67 だが、他の系列が潰れるので 20 で頭打ちにして描いている)、計数列と交わる帯だけなら見逃しだけ。
↑ The animation ―― 動画(768 × 422、10 fps、210 コマ = 撮った速さ): 2 車線の道路を 18 秒。上段はカメラの画に前景マスク(橙)と計数列(黄の縦線)を重ねたもの、下段は 2 車線の計数行を時間方向に積んだスリット画像 (t, x) が上から伸びていく —— 車 1 台が斜めの帯 1 本になり、傾きが速度。見出しの数字は時刻までの累積で、最後は 真値 10 / 仮想ループ 10 / スリット法 10 台と同点(途中で真値が遅れて見えるのは、真値を車体の中心で、ループとスリットを車体の先端で数えるため)。フレームごとに連結成分を数えるゼロ点は最大 7 で、「いま写っている数」を数えているだけ。
py -3.11 examples/poc_traffic_counting.py
Source: examples/poc_traffic_counting.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · blob_features · blob_label · data_to_pixel · text_box · threshold · ticks
A synthetic warehouse floor plan with 26 tracked workers and AGVs, in which replenishment waits, queueing, aisle interference, system waits and stockouts were planted with known times and durations, then read as one (t, y, x) volume. Of the naive 321.5 s of “total dwell”, only 198.0 s (61.6 %) is real waiting; the rest is productive work and creep. Removing all queueing or all aisle interference moves that single number by -39.0 s versus -38.0 s, so it cannot say which to fix, while the per-kind counts drop to zero only for the kind that was removed. A stockout moves dwell by -17.0 s but adds 94.6 m of walking, and the three degradation axes each kill a different kind: sampling interval kills the short waits, occlusion kills the waits pressed against racks, and ID merging kills the two kinds defined by a relation between two people.
↑ Where the Warehouse Dwell Came From — Count Waiting Without Its Kind and Everything Is Just Congestion ―― A synthetic warehouse floor plan with 26 tracked workers and AGVs, in which replenishment waits, queueing, aisle interference, system waits and stockouts were planted with known times and durations, then read as one (t, y, x) volume. Of the naive 321.5 s of “total dwell”, only 198.0 s (61.6 %) is real waiting; the rest is productive work and creep. Removing all queueing or all aisle interference moves that single number by -39.0 s versus -38.0 s, so it cannot say which to fix, while the per-kind counts drop to zero only for the kind that was removed. A stockout moves dwell by -17.0 s but adds 94.6 m of walking, and the three degradation axes each kill a different kind: sampling interval kills the short waits, occlusion kills the waits pressed against racks, and ID merging kills the two kinds defined by a relation between two people.
↑ The measurement ―― ヒートマップは場所を当てるが、「1 人が長く待った」と「何人も短く止まった」を分けない。 (figure labels are in Japanese; the numbers are the same)
↑ ゼロ点はこの表を 1 つの数字に畳む。
↑ 短い待ち(通路の干渉・欠品)から先に消える。
↑ 天井カメラ 2 台。
↑ 上段は通路が明るい(人が通っただけ)。
py -3.11 examples/poc_warehouse_flow.py
Source: examples/poc_warehouse_flow.py
This run produced 12 figures in total - see them all
Ops used (notes): esdf · face_areas · mesh_volume · occupancy_grid · render_volume_projection · vol_dilate · vol_erode · vol_label · vol_opening_ball · vol_region_props
The arrival-time surface of wavefronts spreading from point sources, extracted as an isosurface of the (x, y, t) volume. The null (first frame above threshold) is biased by Δt/2, which more frames cannot remove; linear sub-frame interpolation is 27x better (0.0209 ms). Parabolic interpolation loses to linear, and linear is at its best (a 3.8x margin) when the threshold sits at the Gaussian’s inflection point θ = 0.6065.
↑ The Arrival-Time Surface as an Isosurface in (x, y, t) ―― The left panel of the lead image shows only the last 1 ms at a display time of 3 ms (5.9 px of travel): sparse moving-edge ON events are orange, OFF events cyan, and no-event pixels grey. The middle panel is the time surface and the right panel keeps the whole-clip count. The arrival video has 171 frames at 624 x 586 and 20 fps.
↑ The measurement ―― 方向反転では速度が下がりイベント率も下がる。静止背景はイベントを出さない。 (figure labels are in Japanese; the numbers are the same)
↑ 両対数。
↑ 変曲点 θ=0.6065 では線形が 3.8 倍勝ち、θ=0.2 では放物線が 3.6 倍勝つ。
↑ 各帯の中央値。
↑ 差は上下 99 % 分位(±0.0327 ms)で切った。
↑ The animation ―― 動画(624 × 586、20 fps、171 コマ): 2 つの点源から波面が広がる(源 B は 4 ms 遅れて点火)。左が波面の強度、右は線形補間で出した到達時刻を、波面が通り過ぎた画素から順に塗ったもの —— 動画 (t, y, x) を1 つの体積とみなしたときの等値面が、こうして 1 枚の面になる。点線は撮る前に閉形式で予測した合流線。下段は行 72 の断面で、白 = 真値、橙 = ゼロ点(初めてしきい値を超えたコマの時刻、1 ms の階段)、青 = 線形補間。最後に全画素の誤差: ゼロ点 RMS 0.568 ms(偏り +0.489)、線形補間 RMS 0.0209 ms(27 倍)。
py -3.11 examples/poc_xyt_event_surface.py
Source: examples/poc_xyt_event_surface.py
This run produced 7 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · data_to_pixel · text_box · ticks · vertex_normals · vol_edge_probe
Video Summagator (Nguyen, Niu and Liu, ACM CHI 2012) turns a clip into an (x, y, t) cube, renders the static background faintly and moving objects densely, and lets you cut and rotate the cube to jump to a scene. The new family videocube re-implements it in 6 ops (numpy + scipy only, no new types): frame-difference magnitude as opacity (video_spacetime_cube), front-to-back alpha compositing from any viewpoint with trails coloured by time (blue = start, red = end; vol_render_transfer), cuts (video_cube_cut: x-t slit scans), orbits (video_cube_orbit), keyframes (video_summary_keyframes) and animated GIF export (video_write_gif, the reusable exit). On a synthetic surveillance clip with three objects of known row, onset and speed, the onset read from the first row of each slit-scan streak and the speed read from its slope match the truth exactly (±0 frames; +2.00 / −1.50 / +1.00 px/frame), and the keyframes catch all three onsets (a random choice of 4 frames catches 0.21 on average). The same operators applied to a z-stack of fly-brain EM sections (CREMI sample A, 32 sections, raw data never committed) turn membranes into depth-coloured tubes with neurites running through the stack. In Studio, Tools ▸ Video cube runs it interactively (drag to orbit, slide the cut plane, click the slit scan to jump to that frame, open .npy / GIF / video / .hdf stacks, Save GIF).
↑ A Clip as a Space-Time Cube — What Passed Where, and When, in One Solid ―― Video Summagator (Nguyen, Niu and Liu, ACM CHI 2012) turns a clip into an (x, y, t) cube, renders the static background faintly and moving objects densely, and lets you cut and rotate the cube to jump to a scene. The new family videocube re-implements it in 6 ops (numpy + scipy only, no new types): frame-difference magnitude as opacity (video_spacetime_cube), front-to-back alpha compositing from any viewpoint with trails coloured by time (blue = start, red = end; vol_render_transfer), cuts (video_cube_cut: x-t slit scans), orbits (video_cube_orbit), keyframes (video_summary_keyframes) and animated GIF export (video_write_gif, the reusable exit). On a synthetic surveillance clip with three objects of known row, onset and speed, the onset read from the first row of each slit-scan streak and the speed read from its slope match the truth exactly (±0 frames; +2.00 / −1.50 / +1.00 px/frame), and the keyframes catch all three onsets (a random choice of 4 frames catches 0.21 on average). The same operators applied to a z-stack of fly-brain EM sections (CREMI sample A, 32 sections, raw data never committed) turn membranes into depth-coloured tubes with neurites running through the stack. In Studio, Tools ▸ Video cube runs it interactively (drag to orbit, slide the cut plane, click the slit scan to jump to that frame, open .npy / GIF / video / .hdf stacks, Save GIF).
↑ The measurement ―― the clip as a space-time cube (time = depth to the right): moving objects leave trails coloured by time (blue = start, red = end); the static background is a faint grey (figure labels are in Japanese; the numbers are the same)
↑ x-t slit scans of the three rows: a streak’s first row is the onset, its slope is the speed (yellow line = injected onset)
↑ video_summary_keyframes picks the frames right after each object appears; the head-on cube is the whole clip in one image
↑ a real clip from this repo (a turntable GIF, 40 frames of 160x160): a rotating object becomes a helix in the space-time cube
↑ the same cube operators on a z-stack of EM sections (CREMI sample A, 32 sections of 256^2 (adult Drosophila FAFB)): membranes become tubes running thr…
↑ The animation ―― the EM stack rotating: neurites are the tubes, coloured by depth; the same operator that rotated the surveillance clip
py -3.11 examples/poc_video_cube.py
Source: examples/poc_video_cube.py
This run produced 7 figures in total - see them all
Ops used (notes): intensity · video_cube_cut · video_cube_orbit · video_spacetime_cube · video_summary_keyframes · video_write_gif · vol_render_transfer
A video generator invents plausible motion. The new family live4d (14 ops, numpy + scipy only, one new word: volseq = a volume series (T, Z, Y, X)) invents nothing; instead it offers four ways to make real motion visible, each pinned to numbers on synthetic series with known truth. (1) Magnify: a shell whose radius beats by 0.1 voxel (invisible) is magnified x8 by volseq_magnify_motion (a 3-D version of Wu et al.’s linear Eulerian magnification); the radius amplitude read back is 7.89 x and the period is unchanged. (2) Flow: the displacement field of two blobs separating at a known ±0.75 voxel/frame (vol_flow_3d, 3-D Lucas–Kanade) reads +0.776 / −0.776 where there is gradient, and the pathlines (volseq_pathline_render) become one solid coloured by time. (3) Interpolate: a series made at twice the rate, halved and refilled by volseq_interpolate_flow, matches the removed frames with RMSE 0.0016 when the motion per step is twice the blob size (a linear blend: 0.0207) — while below about 0.8 sigma per step the blend is as good and the warp’s resampling costs a little (plotted honestly). (4) Height: from a focus-sweep series (a moving bump) focus_sweep_height_video recovers the height field with RMSE 0.31 planes. Live cells in 3D+t from the Cell Tracking Challenge (Fluo-N3DH-CHO, raw data never committed) run through the same path.
↑ Living Tissue in 3D+t Without a Generator — Magnify, Flow, Interpolate, Height, All Against Truth ―― A video generator invents plausible motion. The new family live4d (14 ops, numpy + scipy only, one new word: volseq = a volume series (T, Z, Y, X)) invents nothing; instead it offers four ways to make real motion visible, each pinned to numbers on synthetic series with known truth. (1) Magnify: a shell whose radius beats by 0.1 voxel (invisible) is magnified x8 by volseq_magnify_motion (a 3-D version of Wu et al.’s linear Eulerian magnification); the radius amplitude read back is 7.89 x and the period is unchanged. (2) Flow: the displacement field of two blobs separating at a known ±0.75 voxel/frame (vol_flow_3d, 3-D Lucas–Kanade) reads +0.776 / −0.776 where there is gradient, and the pathlines (volseq_pathline_render) become one solid coloured by time. (3) Interpolate: a series made at twice the rate, halved and refilled by volseq_interpolate_flow, matches the removed frames with RMSE 0.0016 when the motion per step is twice the blob size (a linear blend: 0.0207) — while below about 0.8 sigma per step the blend is as good and the warp’s resampling costs a little (plotted honestly). (4) Height: from a focus-sweep series (a moving bump) focus_sweep_height_video recovers the height field with RMSE 0.31 planes. Live cells in 3D+t from the Cell Tracking Challenge (Fluo-N3DH-CHO, raw data never committed) run through the same path.
↑ The measurement ―― radius of the shell read from each volume: the measured beat (0.10 voxel) is below one voxel; after magnification it follows alpha x truth (figure labels are in Japanese; the numbers are the same)
↑ pathlines of particles carried by the 3-D flow of the dividing blob, coloured by time (blue = start, red = end); the faint grey is the first volume
↑ a frame removed from a 2x-rate series (motion 5 voxel = 2 sigma per step) re-created two ways (max projections): the linear blend shows two ghosts per…
↑ error of the re-created frames against the removed ones, as the motion per step grows: below about one blob width a linear blend is as good (the warp’…
↑ Fluo-N3DH-CHO/01 (12 volumes of (5, 111, 128), y/x 1/4): pathlines of the 3-D flow between consecutive volumes, coloured by time
↑ The animation ―― the same pathlines orbited: two straight bundles leaving the split point at +/-0.75 voxel/frame
↑ The animation ―― height field recovered from a focus sweep series (a moving bump, 11 planes), shaded and coloured by height (blue = low, red = high); RMSE 0.31 planes
↑ The animation ―― Fluo-N3DH-CHO/01 (12 volumes of (5, 111, 128), y/x 1/4): the live volumes orbited while time advances (intensity as opacity)
py -3.11 examples/poc_live4d.py
Source: examples/poc_live4d.py
This run produced 10 figures in total - see them all
Ops used (notes): blend · focus_sweep_height_video · focus_sweep_surface_video · vol_flow_3d · volseq_interpolate_flow · volseq_magnify_motion · volseq_pathline_orbit · volseq_pathline_render · volseq_render_orbit · volseq_synth_beating · volseq_synth_dividing
| The physics-simulation × Fullseye series. The primary source is the LaTeX source of an analytic diabolo model for robot learning (von Drigalski et al., ICRA 2021, arXiv:2011.09068; the state-transition conditions exist only in a figure, which was read as an image). The public implementation (BSD-3) was read for comparison only; no code was copied. The model is a point mass + an auxiliary spheroid formed by the string + gravity; one step = forward Euler → project back if outside the spheroid → spin by eq. (2) ω_t = ω_{t−1} + μ Δ_string. Learning-free, all rules. New module diabolo, 16 ops plus 1 mujoco facade. ★Where the paper as printed does not hold (found by transcribing it and putting gates on it): (1) eq. (1b) b = √(a² − | x_L − x_R | /2) subtracts a length from a squared length; at the paper’s real dimensions (string 1.45 m, stick gap 1.10 m) the radicand is negative = NaN. Fixed with the ellipse identity a² = b² + c² (at 0.3 m the printed form gives 0.613 m, the correct value is 0.709 m). (2) LOOSE → ON with s < c_F as drawn overlaps the ON → LOOSE band and flips every step → read as s ≤ c_L. (3) Eq. (2) telescopes and is step-independent (16.57 → 16.59 rad/s for 2 → 1 ms steps), whereas the public implementation’s spin rule scales inversely with the step (ratio 2.02); with μ = 1/r, eq. (2) equals rolling without slipping. (4) The public implementation’s normal is not the gradient and is off by up to 12.2° — here the nearest point and the true normal are used, as in the text. Ground truth comes from outside on three fronts. (1) Closed forms: the focal identity d_L + d_R = l (7.5e-16), the forward-Euler drift g t dt/2 in flight, pendulum periods 2π√(b/g) = 1.379 s sideways and 2π√(a²/(bg)) = 2.116 s lengthwise, the static tension m g a/(2b), and the catch time of parabola × spheroid × the plane through the sticks (closed form 0.7214 s, paper model 0.7230 s). (2) An exact string model (our addition): the unilateral constraint d_L + d_R ≤ l of an inextensible string by RATTLE, with an energy spread of 5.3e-6, a power balance of 1.1e-4 and the tension multiplier matching the closed form to 3.6e-11. (3) A second implementation = MuJoCo spatial tendons (–full): within 0.62 / 1.15 / 3.10 mm of the exact string for linear acceleration / swing / throw (excluding 0.3 s after the catch — MuJoCo’s soft constraint bounces 40 mm), static tension 3.5e-7. Vision is learning-free (ray-traced synthetic video): the axis and centre come from fitting the exact perspective polygon moments of two circles — the near cup rim and the bottom plate — to the mask moments (42 poses, 0–38° from the line of sight × 6 azimuths: max 0.295°, median 0.059°, centre 0.7 mm); at 45° the bottom plate is hidden by the wall and a 2.80 px residual raises the alarm. Spin comes from the phase of markers on the inner surface plus the width of the motion-blur arc to pick the branch (120 fps, 2 ms exposure: 10–110 rev/s within 0.81 %; phase alone folds 70 / 90 / 110 rev/s, beyond the 60 rev/s Nyquist, to −50 / −30 / −10 = the wagon-wheel illusion). Tension comes from the V of stick-tip markers and the axle (0.46 % from the closed form). Tracking a throw (3 m away, 41 frames) gives a centre RMS of 8.9 mm and a median axis error of 1.71°, and the state machine run on the tracked positions agrees 100 % with the true states. Figures: a GIF of the throw with the axis read from the image, the true axis, the state and a 3× inset; the sag (fixed vs printed equation, the NaN boundary); tension (closed form, exact string, image, MuJoCo); heights and differences of the three models; the step dependence of the spin rules; with –full: a GIF of the spin illusion (phase only vs branch picked by the blur arc), a table of axis errors (tilt × azimuth) and a table of where it breaks. 18 gates (default, 3.1 s) plus 7 with –full (42 s). Honestly: the outside truths are the paper’s equations, closed forms and MuJoCo only; nothing is matched against real footage or public data (errors in the equations were found, but whether the model fits a real diabolo is not checked). The μ and damping of eq. (2) and the attitude (tilt and precession) are in neither the paper nor the model; the axis read from the image is the pose given at render time, not a result of the dynamics. Rendering is idealised (flat colours, Lambert shading, plain background), and the axis estimate needs the inside of the near cup to be visible (up to 38°). The string is a point; wrapping on the axle, string mass and stretch are not modelled. Traps we hit: the throw-tracking centre error was printed in metres labelled as mm (100× better than at close range was the giveaway), threshold-based marker detection dropped to zero above 30 rev/s under motion blur, and the exact model and MuJoCo were first built as a bead threaded on the string, which bounced at the apex. |
| *↑ Transcribing a Diabolo Model from the Paper and Checking It, then Reading Axis, Spin and Tension from Synthetic Video — Eq. (1b) as Printed Is Dimensionally Wrong and Gives NaN at the Real Dimensions; Closed Forms, an Exact String and MuJoCo as the Truth ―― The physics-simulation × Fullseye series. The primary source is the LaTeX source of an analytic diabolo model for robot learning (von Drigalski et al., ICRA 2021, arXiv:2011.09068; the state-transition conditions exist only in a figure, which was read as an image). The public implementation (BSD-3) was read for comparison only; no code was copied. The model is a point mass + an auxiliary spheroid formed by the string + gravity; one step = forward Euler → project back if outside the spheroid → spin by eq. (2) ω_t = ω_{t−1} + μ Δ_string. Learning-free, all rules. New module diabolo, 16 ops plus 1 mujoco facade. ★Where the paper as printed does not hold (found by transcribing it and putting gates on it): (1) eq. (1b) b = √(a² − | x_L − x_R | /2) subtracts a length from a squared length; at the paper’s real dimensions (string 1.45 m, stick gap 1.10 m) the radicand is negative = NaN. Fixed with the ellipse identity a² = b² + c² (at 0.3 m the printed form gives 0.613 m, the correct value is 0.709 m). (2) LOOSE → ON with s < c_F as drawn overlaps the ON → LOOSE band and flips every step → read as s ≤ c_L. (3) Eq. (2) telescopes and is step-independent (16.57 → 16.59 rad/s for 2 → 1 ms steps), whereas the public implementation’s spin rule scales inversely with the step (ratio 2.02); with μ = 1/r, eq. (2) equals rolling without slipping. (4) The public implementation’s normal is not the gradient and is off by up to 12.2° — here the nearest point and the true normal are used, as in the text. Ground truth comes from outside on three fronts. (1) Closed forms: the focal identity d_L + d_R = l (7.5e-16), the forward-Euler drift g t dt/2 in flight, pendulum periods 2π√(b/g) = 1.379 s sideways and 2π√(a²/(bg)) = 2.116 s lengthwise, the static tension m g a/(2b), and the catch time of parabola × spheroid × the plane through the sticks (closed form 0.7214 s, paper model 0.7230 s). (2) An exact string model (our addition): the unilateral constraint d_L + d_R ≤ l of an inextensible string by RATTLE, with an energy spread of 5.3e-6, a power balance of 1.1e-4 and the tension multiplier matching the closed form to 3.6e-11. (3) A second implementation = MuJoCo spatial tendons (–full): within 0.62 / 1.15 / 3.10 mm of the exact string for linear acceleration / swing / throw (excluding 0.3 s after the catch — MuJoCo’s soft constraint bounces 40 mm), static tension 3.5e-7. Vision is learning-free (ray-traced synthetic video): the axis and centre come from fitting the exact perspective polygon moments of two circles — the near cup rim and the bottom plate — to the mask moments (42 poses, 0–38° from the line of sight × 6 azimuths: max 0.295°, median 0.059°, centre 0.7 mm); at 45° the bottom plate is hidden by the wall and a 2.80 px residual raises the alarm. Spin comes from the phase of markers on the inner surface plus the width of the motion-blur arc to pick the branch (120 fps, 2 ms exposure: 10–110 rev/s within 0.81 %; phase alone folds 70 / 90 / 110 rev/s, beyond the 60 rev/s Nyquist, to −50 / −30 / −10 = the wagon-wheel illusion). Tension comes from the V of stick-tip markers and the axle (0.46 % from the closed form). Tracking a throw (3 m away, 41 frames) gives a centre RMS of 8.9 mm and a median axis error of 1.71°, and the state machine run on the tracked positions agrees 100 % with the true states. Figures: a GIF of the throw with the axis read from the image, the true axis, the state and a 3× inset; the sag (fixed vs printed equation, the NaN boundary); tension (closed form, exact string, image, MuJoCo); heights and differences of the three models; the step dependence of the spin rules; with –full: a GIF of the spin illusion (phase only vs branch picked by the blur arc), a table of axis errors (tilt × azimuth) and a table of where it breaks. 18 gates (default, 3.1 s) plus 7 with –full (42 s). Honestly: the outside truths are the paper’s equations, closed forms and MuJoCo only; nothing is matched against real footage or public data (errors in the equations were found, but whether the model fits a real diabolo is not checked). The μ and damping of eq. (2) and the attitude (tilt and precession) are in neither the paper nor the model; the axis read from the image is the pose given at render time, not a result of the dynamics. Rendering is idealised (flat colours, Lambert shading, plain background), and the axis estimate needs the inside of the near cup to be visible (up to 38°). The string is a point; wrapping on the axle, string mass and stretch are not modelled. Traps we hit: the throw-tracking centre error was printed in metres labelled as mm (100× better than at close range was the giveaway), threshold-based marker detection dropped to zero above 30 rev/s under motion blur, and the exact model and MuJoCo were first built as a bead threaded on the string, which bounced at the apex.* |
↑ The measurement ―― 式 (1b) の原文 √(a² − d/2) は長さと長さの 2 乗の差で次元が合わず、d > 2a² = 1.051 m で NaN(論文の実機の間隔 1.10 m は NaN の側)。直した √(a² − c²)(楕円の恒等式)が実線。 (figure labels are in Japanese; the numbers are the same)
↑ 閉形式 T = m g a/(2b)(破線)に、厳密な糸の模型の乗数と、合成映像の棒の先と軸の V 字から読んだ張力(最大 0.46 % 差)を重ねる。
↑ 同じ棒の動き(0.30〜0.50 s で開いて持ち上げ、張りつめたまま受ける)での高さ。
↑ 式 (2) は望遠鏡和なので刻み 1 ms と 0.5 ms の線が重なる。
↑ 視線からの傾き × 像の上の方位で、画像だけから読んだ軸と描いた時の軸の角(縁の半径 ≈ 48 px)。
↑ The animation ―― 回転を上げると 120 fps の像ではマーカーが止まり、逆に回って見える(車輪の錯視)。位相だけの読みはナイキスト 60 rev/s で折り返し、回転ぶれの弧の幅で枝を選んだ読みは真値に付いていく(320 × 240、等倍)。
py -3.11 examples/poc_diabolo_model_and_vision.py
Source: examples/poc_diabolo_model_and_vision.py
This run produced 9 figures in total - see them all
Ops used (notes): arrow · diabolo_axis_from_image · diabolo_camera · diabolo_dynamics_step · diabolo_marker_phase · diabolo_params · diabolo_render · diabolo_scene_mjcf · diabolo_simulate · diabolo_spheroid · diabolo_spin_from_markers · diabolo_state_sequence · diabolo_throw_catch_truth · diabolo_track · measure_text · spheroid_closest · string_tension_from_sag · string_tension_static · text_box · zoom_inset
| Move the robots of a swarm as SPH (smoothed-particle hydrodynamics) particles and the slowdown of the individuals that hit an obstacle travels upstream as a crowding pressure; around a cylinder the swarm then flows approximately as a potential flow (a weakly compressible swarm with a strong pull back to the free stream is a Darcy flow — a derivation). The new module swarmflow (12 ops) measures the velocity field from overhead video, either by detecting and tracking individuals (blob2d) or by cross-correlation PIV (pivops), and reads the impact point (the stagnation point) and the obstacle’s centre and radius from how the flow falls short of the free stream. The references are closed forms: potential flow around a cylinder (surface speed off 2U | sin θ | by 2e-7), the normalisation ∫W dV = 1 of the SPH cubic-spline kernel (within 1e-12 in 1, 2 and 3 dimensions), and Ritter’s dam-break solution. On the closed-form field the 2-D fit is exact, and linearising 1/√d along the centre line gives the centre to 0.30 % of R and the radius to +0.09 % (taking the speed at the upstream edge as U gives −16 % — the cylinder’s deficit only decays as 1/r², so U is divided back out by the closed form, iteratively). From particle video, PIV gives a velocity rms of 2.1 % of U and a radius of −0.6 %; tracking gives 3.3 % and +0.3 %. On noise only, a vortex or a source, the explained fraction is 0.002–0.080 and nothing is claimed; with an obstacle it is at least 0.761 (threshold 0.5). PIV on a lattice-like swarm grows correlation peaks at the neighbouring lattice positions: 25 % rms unchecked, 2.9 % with a peak ratio of 1.1 and a median test. In a simulated swarm of 1,692 individuals the velocity measured from video matches the individuals’ true velocity to 1.4 % rms, and a fit to the upstream side only (before contact) gives the centre to 4.5 % of R and the radius to −10 %; with no obstacle it claims nothing (0.017). Honestly: a swarm is not an exact potential flow — an empty wake stretches behind the obstacle, the surface speed reaches about 1.3U rather than 2U, and the radius comes out 10 % small. The overhead video does show the hole at the obstacle; the estimate does not use it and reads the velocities alone (the value is knowing before contact). Circular obstacles only, with the free stream along +x. |
| *↑ A Swarm That Flows Like a Fluid Senses an Invisible Obstacle from the Disturbance of Its Velocity Field Alone — Reading the Impact Point and the Obstacle Outline by Tracking and PIV (particle image velocimetry) on Overhead Video, from Upstream Before Contact, and Saying Nothing When There Is No Obstacle ―― Move the robots of a swarm as SPH (smoothed-particle hydrodynamics) particles and the slowdown of the individuals that hit an obstacle travels upstream as a crowding pressure; around a cylinder the swarm then flows approximately as a potential flow (a weakly compressible swarm with a strong pull back to the free stream is a Darcy flow — a derivation). The new module swarmflow (12 ops) measures the velocity field from overhead video, either by detecting and tracking individuals (blob2d) or by cross-correlation PIV (pivops), and reads the impact point (the stagnation point) and the obstacle’s centre and radius from how the flow falls short of the free stream. The references are closed forms: potential flow around a cylinder (surface speed off 2U | sin θ | by 2e-7), the normalisation ∫W dV = 1 of the SPH cubic-spline kernel (within 1e-12 in 1, 2 and 3 dimensions), and Ritter’s dam-break solution. On the closed-form field the 2-D fit is exact, and linearising 1/√d along the centre line gives the centre to 0.30 % of R and the radius to +0.09 % (taking the speed at the upstream edge as U gives −16 % — the cylinder’s deficit only decays as 1/r², so U is divided back out by the closed form, iteratively). From particle video, PIV gives a velocity rms of 2.1 % of U and a radius of −0.6 %; tracking gives 3.3 % and +0.3 %. On noise only, a vortex or a source, the explained fraction is 0.002–0.080 and nothing is claimed; with an obstacle it is at least 0.761 (threshold 0.5). PIV on a lattice-like swarm grows correlation peaks at the neighbouring lattice positions: 25 % rms unchecked, 2.9 % with a peak ratio of 1.1 and a median test. In a simulated swarm of 1,692 individuals the velocity measured from video matches the individuals’ true velocity to 1.4 % rms, and a fit to the upstream side only (before contact) gives the centre to 4.5 % of R and the radius to −10 %; with no obstacle it claims nothing (0.017). Honestly: a swarm is not an exact potential flow — an empty wake stretches behind the obstacle, the surface speed reaches about 1.3U rather than 2U, and the radius comes out 10 % small. The overhead video does show the hole at the obstacle; the estimate does not use it and reads the velocities alone (the value is knowing before contact). Circular obstacles only, with the free stream along +x.* |
Terms: PIV — A flow-measurement method that estimates a velocity field from the displacement of particle patterns between images separated by a short time.
↑ The measurement ―― 群れ(白い点、1692 個体)が左から右へ流れる。障害物は描いていない —— 群れには当たる壁として効くだけ。直近 10 コマの個体の追跡から速度場を作り、自由流より遅い所を橙、速い所を青で塗る。上流だけの当てはめで推定した障害物の輪郭(水色の輪)と衝突点(桃色の×)が浮かび上がる。最後の 8 コマで真の障害物を白の破線で重ねる(半径は 1 割ほど小さめに出る —— 群れの流れは厳密なポテンシャル流ではないため)。 (figure labels are in Japanese; the numbers are the same)
↑ 中心線の上の欠損 d = R²/(x − x_c)² は 1/√d にすると直線になり(傾き −1/R)、1/√d = 1 の所がよどみ点(衝突点)。
↑ 個体を減らすと間隔 s が広がる。
↑ ゲートを開けた群れの広がりの模型(1 次元 SPH の浅水、400 粒子)。
py -3.11 examples/poc_swarm_obstacle_from_flow.py
Source: examples/poc_swarm_obstacle_from_flow.py
This run produced 5 figures in total - see them all
Ops used (notes): blob_label · obstacle_fit_doublet · piv_cross_correlate · potential_flow_cylinder · ritter_dam_break · sph_dam_break_1d · sph_kernel · stagnation_from_centerline · swarm_field_from_piv · swarm_field_from_tracks · swarm_render_overhead · swarm_simulate · text_box · velocity_deficit_map
| Robot table tennis has had the same visual stance since 1988: find the ball in several cameras, triangulate it into a 3-D point, look ahead with an equation of motion that includes drag and Magnus lift, predict across the bounce and, if you can, measure spin from the markings. This exhibit builds that whole chain from numpy ops and scores it against a truth the world itself holds (ball centre, pose, contact time and angular velocity are formulas fixed at generation). First the theorems of mechanics: with drag and Magnus set to zero the RK4 flight matches the closed-form parabola to 1e-12, and the least-squares parabola returns g = 9.81 to 4.0e-15. Over 500 random impacts (e ∈ [0.3, 1], μ ∈ [0, 0.6]) the angular momentum about the contact point is conserved to a relative 4.9e-16 at worst, 136 impacts reach rolling and 364 keep sliding. Dropped from 30.5 cm with e = 0.90 the apexes are 24.7, 20.0, 16.2 and 13.1 cm, within 3.0e-07 of the closed form e^{2k}h₀; the first bounce of 24.7 cm sits inside the ITTF 24–26 cm rule, both the apex list and the contact intervals give back e = 0.900000, and the total time to rest, 4.736 s, sits beside the closed-form 4.738 s. Then a 40 mm ball with 14 markings is placed on an ITTF table and a topspin shot (ω = 240 rad/s) is filmed at 100 fps for 0.6 s by two cameras plus one close-up (rendering 61 frames × 2 cameras took 22.0 s). The true contact is at t = 0.3094 s, point (0.507, 0.052), v [5.15, −0.28, −2.65] → [5.01, −0.17, 2.39], ω [0, 240, 0] → [8.5, 250.6, 0] (grip). Chromaticity detection finds the ball in 122 / 122 frames with a centre error of 0.152 px at the median, 0.299 px at the 90th percentile and 2.094 px at worst (image radius ≈ 4.3 px). DLT triangulation is 3.8e-15 m from the truth’s own projections and, from the detections, 1.61 mm at the median, 4.25 mm at the 90th percentile and 23.56 mm at worst (median reprojection rms 0.094 px). The constant-acceleration Kalman filter is exact for a parabola, so its innovation on the parabolic truth is at most 8.2e-09 m (2.3e-03 m on the drag-plus-Magnus truth, which lies outside the model), and its velocity from the detections is off by 0.074 m/s at the median ( | v | ≈ 5.9 m/s). The local minimum of z lands at t = 0.31, 0.6 ms from the truth. Fitting the initial state by a 6-parameter Gauss–Newton to the 15 frames (0.15 s) before the bounce (the true trajectory comes back to 3.2e-13) and predicting across the bounce puts the landing point 0.4 cm off (0.5 ms in time) when the true spin is known, 20.2 cm off when spin is ignored, and 6.6 cm off with a parabola even given the true spin — the difference is the Magnus term. On the close-up camera at the bounce (1000 fps, strobe) 19 / 19 frame pairs pair at least two markings, and the Kabsch rotation gives a median angular velocity of [1.7, 256.6, −2.1] rad/s against the truth [8.5, 250.6, 0] ( | ω | 2394 rpm), a relative error of 3.7 %. The coefficient of restitution from equation-of-motion fits to 12 frames on each side gives v_z −2.637 → 2.403, e = 0.9114 (truth 0.90). Honestly: a parabolic fit gives 0.9398, 4.4 % off, because it absorbs drag and Magnus into g; the ball detection runs on synthetic footage with a known colour and no real lighting, blur or background; and the aerodynamic coefficients are textbook values with the truth built from the same formula (this is not a test of aerodynamics). 10 gates, 42.0 s. Finally two freely moving rackets (15 × 16 cm blades, ≤ 6 m/s) rally against each other: a feeder that returns near its previous target keeps the rally to the 12-hit cap, while an attacker aiming fast at the far corners ends it after 9 hits (a rewind mechanism previews each shot with the true physics and retries a miss; the attacker still misses after 99 rewinds). Gaussian noise on the perceived position leaves the rally length unchanged up to 5 cm (the blade’s margin) and cuts it to 2 hits at 10 cm — the rally length scores perception, prediction and control as one number. |
| *↑ Measuring a Table-Tennis Ball the Way the Pioneers Did — Multi-Camera Tracking, Triangulation, Trajectory Prediction, Bounce and Spin, Scored by Theorems on a Table with Ground Truth ―― Robot table tennis has had the same visual stance since 1988: find the ball in several cameras, triangulate it into a 3-D point, look ahead with an equation of motion that includes drag and Magnus lift, predict across the bounce and, if you can, measure spin from the markings. This exhibit builds that whole chain from numpy ops and scores it against a truth the world itself holds (ball centre, pose, contact time and angular velocity are formulas fixed at generation). First the theorems of mechanics: with drag and Magnus set to zero the RK4 flight matches the closed-form parabola to 1e-12, and the least-squares parabola returns g = 9.81 to 4.0e-15. Over 500 random impacts (e ∈ [0.3, 1], μ ∈ [0, 0.6]) the angular momentum about the contact point is conserved to a relative 4.9e-16 at worst, 136 impacts reach rolling and 364 keep sliding. Dropped from 30.5 cm with e = 0.90 the apexes are 24.7, 20.0, 16.2 and 13.1 cm, within 3.0e-07 of the closed form e^{2k}h₀; the first bounce of 24.7 cm sits inside the ITTF 24–26 cm rule, both the apex list and the contact intervals give back e = 0.900000, and the total time to rest, 4.736 s, sits beside the closed-form 4.738 s. Then a 40 mm ball with 14 markings is placed on an ITTF table and a topspin shot (ω = 240 rad/s) is filmed at 100 fps for 0.6 s by two cameras plus one close-up (rendering 61 frames × 2 cameras took 22.0 s). The true contact is at t = 0.3094 s, point (0.507, 0.052), v [5.15, −0.28, −2.65] → [5.01, −0.17, 2.39], ω [0, 240, 0] → [8.5, 250.6, 0] (grip). Chromaticity detection finds the ball in 122 / 122 frames with a centre error of 0.152 px at the median, 0.299 px at the 90th percentile and 2.094 px at worst (image radius ≈ 4.3 px). DLT triangulation is 3.8e-15 m from the truth’s own projections and, from the detections, 1.61 mm at the median, 4.25 mm at the 90th percentile and 23.56 mm at worst (median reprojection rms 0.094 px). The constant-acceleration Kalman filter is exact for a parabola, so its innovation on the parabolic truth is at most 8.2e-09 m (2.3e-03 m on the drag-plus-Magnus truth, which lies outside the model), and its velocity from the detections is off by 0.074 m/s at the median ( | v | ≈ 5.9 m/s). The local minimum of z lands at t = 0.31, 0.6 ms from the truth. Fitting the initial state by a 6-parameter Gauss–Newton to the 15 frames (0.15 s) before the bounce (the true trajectory comes back to 3.2e-13) and predicting across the bounce puts the landing point 0.4 cm off (0.5 ms in time) when the true spin is known, 20.2 cm off when spin is ignored, and 6.6 cm off with a parabola even given the true spin — the difference is the Magnus term. On the close-up camera at the bounce (1000 fps, strobe) 19 / 19 frame pairs pair at least two markings, and the Kabsch rotation gives a median angular velocity of [1.7, 256.6, −2.1] rad/s against the truth [8.5, 250.6, 0] ( | ω | 2394 rpm), a relative error of 3.7 %. The coefficient of restitution from equation-of-motion fits to 12 frames on each side gives v_z −2.637 → 2.403, e = 0.9114 (truth 0.90). Honestly: a parabolic fit gives 0.9398, 4.4 % off, because it absorbs drag and Magnus into g; the ball detection runs on synthetic footage with a known colour and no real lighting, blur or background; and the aerodynamic coefficients are textbook values with the truth built from the same formula (this is not a test of aerodynamics). 10 gates, 42.0 s. Finally two freely moving rackets (15 × 16 cm blades, ≤ 6 m/s) rally against each other: a feeder that returns near its previous target keeps the rally to the 12-hit cap, while an attacker aiming fast at the far corners ends it after 9 hits (a rewind mechanism previews each shot with the true physics and retries a miss; the attacker still misses after 99 rewinds). Gaussian noise on the perceived position leaves the rally length unchanged up to 5 cm (the blade’s margin) and cuts it to 2 hits at 10 cm — the rally length scores perception, prediction and control as one number.* |
↑ The measurement ―― カメラ 1 の像での球の軌跡: 真値の投影(線)と検出(点)。中心の誤差の中央値 0.15 px。 (figure labels are in Japanese; the numbers are the same)
↑ x–z 面の軌跡。
↑ 30.5 cm から落とした球(e = 0.90)の頂点: 閉形式 e^{2k}h₀ と 1e-6 で一致し、最初の跳ね 24.7 cm は ITTF の規格 24〜26 cm の中。
↑ 跳ね際の近接カメラ(1000 fps、256 × 256、20°)の 4 コマ。
↑ 知覚(球の位置)にガウス雑音を足したときのラリーの本数(送り合い、上限 8 本): 0 mm → 8 本、50 mm → 8 本、100 mm → 2 本。
↑ The animation ―― 追跡カメラ 1(2 × 2 平均で 512 × 400)、100 fps を 1/10 速で。橙の十字は検出、緑は Kalman の状態の投影、赤は跳ねる前の 15 コマから予測した着地点。球は 2291 rpm のトップスピンで、台で 1 度跳ねる(e = 0.90)。
↑ The animation ―― 自由に動く 2 本のラケット(板 15 × 16 cm、速さ ≤ 6 m/s、加速度 ≤ 60 m/s²)の送り合い(前回に近い少しずらした位置へ返す)、最初の 3 秒を 1/5 速で。相手コートに 1 度跳ねた球を面 x = ±1.55 m で迎え撃ち、狙った点へ運動方程式で返す。この設定では上限 12 本まで続く。攻める側(遠い隅を速く)が入ると 9 本で終わる(out)。
py -3.11 examples/poc_ball_bounce.py
Source: examples/poc_ball_bounce.py
This run produced 8 figures in total - see them all
Ops used (notes): add_ball · apex_sequence · ball_detect · ball_mesh · ball_params · ball_set_pose · ball_track · ball_truth · bounce · bounce_detect · bounce_total_time · camera_rig · contact_angular_momentum · crosshair · fit_parabola · flight_fit · flight_ode · flight_simulate · flight_state_at · flight_vacuum · impact_params · kalman_ca · marker_direction · racket_params … (+12)
Robots have played kendama for thirty years (via-points from a human demonstration in 1996, DMPs plus reinforcement learning for ball-in-a-cup in 2009, the 2020 split into offline swing-up and online catching). This exhibit rebuilds that stance from numpy ops. Shape: from the Japan Kendama Association’s published values (60 mm ball, 70 mm across the cups, 180 mm assembled) and a user-supplied description of the JKA 16-2 model (160 mm ken, cups of 42 / 38 / 35 mm, string from the cross-piece hole; primary source not checked), the ken (spike, neck, shaft through the cross piece, stepped and ringed grip, base cup) and the cross piece (flaring like trumpets into the big and small cups) are built as solids of revolution, plus a ball with a 17 mm hole; every dimension measured from the vertices matches to 1e-9, and with the spike pushed to the bottom of the hole the assembly is 180 mm long (the 40 mm hole depth is derived as 160 + 60 − 180). Theorems: the AGM returns K(0.5) = 1.685750354812596 to 1e-12; the period 4√(L/g)K(sin θ₀/2) is recovered to 3.0e-4 by the string (effective length 0.42 m) and 8.9e-11 by a rod; tension matches its closed form to 0.26 %; the slack angle is 125.04° (closed form 125.26°); the projection’s dissipation is first order in dt (ratio 9.7); the snap loss equals ½mv_r² to 0.2 %. The closed loop acts on images only: two cameras (480 × 360, 100 fps) render the world, the ball is found by chromaticity and triangulated (median error 0.51 mm), slack is declared when the ball is 5 mm closer to the cross-piece hole than the string length for two frames in a row (truth 0.077 s, images 0.100 s), and a gravity-known parabola (6 unknowns) fitted to the frames after that gives the landing point. The true (p, v) is used only to render the world. The controller’s stages are explicit: lift straight up (4.9 g, the ken dodges 10 cm towards the string hole) → wait for slack → wait until the ball’s underside clears the kendama → carry the cup horizontally under the ball → lower it at touchdown. Only gravity and string tension move the ball; if the kendama touches it the trial fails (a lift without the dodge hits the cross piece at 0.260 s). The same plan with only the trick’s pose switched gives, over 20 trials, big cup truth 1.00 / images 1.00 (lateral 2.40 mm), small cup 1.00 / 1.00, base cup 0.95 / 0.95, candle (truth) 0.95; lowering at touchdown cuts the mean relative speed from 0.91 to 0.61 m/s (big cup). The landing-point error falls with the number of post-slack frames, 10.51 mm (3 frames) → 0.98 mm (44). Pixel noise of 0 / 0.5 / 1 / 2 / 8 / 16 px gives 1.00 / 1.00 / 1.00 / 1.00 / 0.90 / 0.40 — flat up to 2 px because the 21 mm cup rim absorbs it, while the lateral offset grows 2.40 → 4.12 → 12.05 mm. The recommended model (49 mm big cup) also scores 1.00. For a stationary ball the hole direction from two views is off by 1.8° at the median and 4.7° at worst. The world was also turned into 3D Gaussian Splatting before recognition (gsplatnp: Gaussians attached to the world’s faces, rendered by EWA projection, front-to-back alpha compositing and Mip-Splatting’s opacity compensation; the Gaussians are built from the true shape rather than learned from photos, and reconstruction imperfections are imitated with spacing and error knobs): on 3DGS images at 4 mm spacing the closed loop caught 1 + 2 trials out of 1 + 2 (median triangulation error 0.30 mm); spacing from 2 to 64 mm leaves ball detection at 1.00 (the curvature cap keeps at least 146 Gaussians on the ball), while position error (0.25 at 20 mm) and colour error (0.00 at 0.3) break it. At 480 × 360 the ball’s hole is smeared away by the 3DGS blur (1 of 40 poses, mesh 14); at twice the resolution and 2 mm spacing it is read in 16 (median error 2.3°), at 4 mm in none — reading the hole needs pixels on the ball and Gaussians inside the hole. The hole is built as a 40 mm deep cavity, not a disc on the surface (as a disc it vanished under the ball’s own Gaussians in 3DGS). Consecutive tricks were added too (the user: “after a catch, carry on from that state and catch in another cup”, “10 successes is enough”): the ball is tossed from the cup it sits in (accelerate the cup up and stop it harder than g, and the ball leaves; the apex matches v²/2g to 2e-6 m), the kendama is turned in flight (wrist ≤ 30 rad/s, assumed) to bring the next cup under the landing point, and a hand controller that targets position and velocity matches the ball’s speed at the catch (a position-only controller catches nothing with a 20 cm apex). On images alone, moshikame (big ↔ base cup) and the three-cup sequence (big → small → base) both run 10 in a row (grade 5 on the association’s moshikame table), relative speed at touchdown 0.41–0.48 m/s, every flight start found from images. Honestly: without noise, 100 in a row is the same cycle repeated; with 2 px of pixel noise moshikame breaks after 3 and the three cups after 6. Honestly: synthetic footage with ground truth, not real video; ball rotation is not simulated (it keeps its last pose once slack); a catch is a geometric test at the moment the ball touches the rim (lateral ≤ rim radius, relative speed ≤ 1 m/s as an assumed threshold, descending) with no bounce or rolling; the 15° tilt of the cup grip, cup depths and the 75 g ball are assumptions. In flight the hole faces down and is almost never seen by both eye-level cameras (0 of 54 frames). Why the candle is harder than the base cup cannot be expressed by a rigid, translating hand. 16 gates, 171.0 s.
↑ Kendama the Way the Pioneers Did It — A True-to-Shape Kendama Filmed by Two Cameras, the Ball’s Flight Predicted from Images Alone and Caught in the Big, Small and Base Cups ―― Robots have played kendama for thirty years (via-points from a human demonstration in 1996, DMPs plus reinforcement learning for ball-in-a-cup in 2009, the 2020 split into offline swing-up and online catching). This exhibit rebuilds that stance from numpy ops. Shape: from the Japan Kendama Association’s published values (60 mm ball, 70 mm across the cups, 180 mm assembled) and a user-supplied description of the JKA 16-2 model (160 mm ken, cups of 42 / 38 / 35 mm, string from the cross-piece hole; primary source not checked), the ken (spike, neck, shaft through the cross piece, stepped and ringed grip, base cup) and the cross piece (flaring like trumpets into the big and small cups) are built as solids of revolution, plus a ball with a 17 mm hole; every dimension measured from the vertices matches to 1e-9, and with the spike pushed to the bottom of the hole the assembly is 180 mm long (the 40 mm hole depth is derived as 160 + 60 − 180). Theorems: the AGM returns K(0.5) = 1.685750354812596 to 1e-12; the period 4√(L/g)K(sin θ₀/2) is recovered to 3.0e-4 by the string (effective length 0.42 m) and 8.9e-11 by a rod; tension matches its closed form to 0.26 %; the slack angle is 125.04° (closed form 125.26°); the projection’s dissipation is first order in dt (ratio 9.7); the snap loss equals ½mv_r² to 0.2 %. The closed loop acts on images only: two cameras (480 × 360, 100 fps) render the world, the ball is found by chromaticity and triangulated (median error 0.51 mm), slack is declared when the ball is 5 mm closer to the cross-piece hole than the string length for two frames in a row (truth 0.077 s, images 0.100 s), and a gravity-known parabola (6 unknowns) fitted to the frames after that gives the landing point. The true (p, v) is used only to render the world. The controller’s stages are explicit: lift straight up (4.9 g, the ken dodges 10 cm towards the string hole) → wait for slack → wait until the ball’s underside clears the kendama → carry the cup horizontally under the ball → lower it at touchdown. Only gravity and string tension move the ball; if the kendama touches it the trial fails (a lift without the dodge hits the cross piece at 0.260 s). The same plan with only the trick’s pose switched gives, over 20 trials, big cup truth 1.00 / images 1.00 (lateral 2.40 mm), small cup 1.00 / 1.00, base cup 0.95 / 0.95, candle (truth) 0.95; lowering at touchdown cuts the mean relative speed from 0.91 to 0.61 m/s (big cup). The landing-point error falls with the number of post-slack frames, 10.51 mm (3 frames) → 0.98 mm (44). Pixel noise of 0 / 0.5 / 1 / 2 / 8 / 16 px gives 1.00 / 1.00 / 1.00 / 1.00 / 0.90 / 0.40 — flat up to 2 px because the 21 mm cup rim absorbs it, while the lateral offset grows 2.40 → 4.12 → 12.05 mm. The recommended model (49 mm big cup) also scores 1.00. For a stationary ball the hole direction from two views is off by 1.8° at the median and 4.7° at worst. The world was also turned into 3D Gaussian Splatting before recognition (gsplatnp: Gaussians attached to the world’s faces, rendered by EWA projection, front-to-back alpha compositing and Mip-Splatting’s opacity compensation; the Gaussians are built from the true shape rather than learned from photos, and reconstruction imperfections are imitated with spacing and error knobs): on 3DGS images at 4 mm spacing the closed loop caught 1 + 2 trials out of 1 + 2 (median triangulation error 0.30 mm); spacing from 2 to 64 mm leaves ball detection at 1.00 (the curvature cap keeps at least 146 Gaussians on the ball), while position error (0.25 at 20 mm) and colour error (0.00 at 0.3) break it. At 480 × 360 the ball’s hole is smeared away by the 3DGS blur (1 of 40 poses, mesh 14); at twice the resolution and 2 mm spacing it is read in 16 (median error 2.3°), at 4 mm in none — reading the hole needs pixels on the ball and Gaussians inside the hole. The hole is built as a 40 mm deep cavity, not a disc on the surface (as a disc it vanished under the ball’s own Gaussians in 3DGS). Consecutive tricks were added too (the user: “after a catch, carry on from that state and catch in another cup”, “10 successes is enough”): the ball is tossed from the cup it sits in (accelerate the cup up and stop it harder than g, and the ball leaves; the apex matches v²/2g to 2e-6 m), the kendama is turned in flight (wrist ≤ 30 rad/s, assumed) to bring the next cup under the landing point, and a hand controller that targets position and velocity matches the ball’s speed at the catch (a position-only controller catches nothing with a 20 cm apex). On images alone, moshikame (big ↔ base cup) and the three-cup sequence (big → small → base) both run 10 in a row (grade 5 on the association’s moshikame table), relative speed at touchdown 0.41–0.48 m/s, every flight start found from images. Honestly: without noise, 100 in a row is the same cycle repeated; with 2 px of pixel noise moshikame breaks after 3 and the three cups after 6. Honestly: synthetic footage with ground truth, not real video; ball rotation is not simulated (it keeps its last pose once slack); a catch is a geometric test at the moment the ball touches the rim (lateral ≤ rim radius, relative speed ≤ 1 m/s as an assumed threshold, descending) with no bounce or rolling; the 15° tilt of the cup grip, cup depths and the 75 g ball are assumptions. In flight the hole faces down and is almost never seen by both eye-level cameras (0 of 54 frames). Why the candle is harder than the base cup cannot be expressed by a rigid, translating hand. 16 gates, 171.0 s.
↑ The measurement ―― 近接カメラ(図のためだけ、けん玉から 0.36 m)で見た 4 技の持ち方の姿勢(けんと皿胴は 1 つの剛体、持つ所だけが違う)。けん(けん先 → 細い首 → 皿胴を貫く胴 → 段と輪のある握り → 中皿)、皿胴の両端がラッパのように開いた大皿(赤)・小皿(紫)、中皿(青)、直径 17 mm・深さ 40 mm の穴(くぼみ)のある玉、皿胴の糸穴から出る糸。寸法は JKA 16-2 型(けん 160 mm、横幅 70 mm、皿 42 / 38 / 35 mm)。玉の位置と向きは見せるために置いたもの。 (figure labels are in Japanese; the numbers are the same)
↑ 真下から 150° 相当の速さ(3.92 m/s)で打ち出した玉のひも(有効長 0.42 m)の張力。
↑ y–z 面(手元を逃がす向き)。
↑ 画像だけの閉ループの成功率(大皿、各 20 試行): 0 px → 1.00、0.5 px → 1.00、1 px → 1.00、2 px → 1.00、8 px → 0.90、16 px → 0.40。
↑ 3DGS の世界の捕球の試行(弛み → 捕球の 12 コマ)を、つまみを振った 3DGS で描き直して色度の検出(balltrack.ball_detect)にかけた。
↑ The animation ―― カメラ 2、100 fps を 1/10 速で(最後のコマで 1 秒止める)。振り上げ → t = 0.077 s にひもが弛む(画像での検出 0.100 s)→ 玉がけんを越えるまで待つ → 皿を水平に運ぶ → 着地で下げる → t = 0.540 s に大皿で受ける。十字は画像の予測(弛んだ後のコマに当てた重力つきの放物線)から読んだ着地点、枠の中は同じカメラでけん玉のまわりを 3 倍の解像度に描き直した窓。t ≈ 0.3 s に玉がけんに重なって見えるのはカメラから見た重なりで、けんは糸穴の側へ 10 cm 逃げて玉の奥にある(その間の隙間の最小 41 mm)。正直に: 捕球は「縁に触れた瞬間に横ずれ ≤ 21 mm・相対速さ ≤ 1 m/s・下降中」の判定で、縁での跳ねと転がりは描いていない。
↑ The animation ―― 右 = 閉ループの知覚が実際に見た画像(世界を 3DGS にして描いたもの、カメラ 2、100 fps を 1/10 速)、左 = 同じ瞬間のメッシュ(真の形)。右上の窓 = 同じカメラでけん玉のまわりを 3 倍の解像度に描き直したもの(左はメッシュ、右は同じ 3DGS)。青の輪 = 色度で検出した玉、十字 = 弛んだ後のコマに当てた重力つきの放物線から読んだ着地点。t = 0.541 s に大皿で受ける(横ずれ 4.34 mm、推定 391 回は全部 3DGS の画像から)。
py -3.11 examples/poc_kendama.py
Source: examples/poc_kendama.py
This run produced 11 figures in total - see them all
Ops used (notes): ball_detect · camera_perceiver · catch_plan_staged · catch_success_rate · crosshair · elliptic_k_agm · gs_from_world · gs_render · gs_render_fn · gs_update · hole_detect · ken_mesh · kendama_clearance · kendama_combo_simulate · kendama_params · kendama_pose · kendama_rig · kendama_simulate · kendama_world · leader_line · overlay_mask · pendulum_launch_speed · pendulum_period_exact · pendulum_rod_simulate … (+10)
Two table-tennis balls launched at the same speed and angle land in different places if they spin differently: topspin dips and lands short, backspin floats and lands long, sidespin curves sideways (the Magnus effect, force along ω × v). This exhibit films four balls that differ only in spin (150 rad/s ≈ 1,430 rpm) with two 240 fps cameras and reads the spin in two ways. From the curve: fit the drag + Magnus equation of motion to the triangulated pre-bounce track with 9 parameters (position, velocity, spin) — the new op fit_spin, a damped least-squares fit. The perpendicular-spin error is 3.9 % (top), 0.9 % (back), 1.7 % (side), and the landing point predicted from the read spin is within 2 cm of the truth for all four (x = [0.488, 0.754, 1.124] m; top < none < back). From the markings: film the same ball with a 1000 fps close-up camera (20 frames) and fit the motion of the 14 black marks with Kabsch — 4.8 %, 3.2 %, 0.9 %. The two readings are independent (one sees only the track, the other only the marks) and agree within 10 % (a second-implementation gate). Theorem gate: Magnus force is ω × v, so spin parallel to the direction of travel produces no force — the instantaneous acceleration differs by less than 1e-12, and after 0.25 s of flight the track moves 7.1 mm (versus 5.4 cm for perpendicular spin). fit_spin therefore returns this component separately, and the 22 rad/s the fit reports for the no-spin ball lies almost entirely along that unreadable direction. With a short track the curve is buried in detection error and the spin cannot be read (400.3 % → 4.0 %). Found on the way: from the server-side camera the net’s white band hides the top half of a low ball beyond the net and shifts the detected centre (up to 26 mm after triangulation) — detections whose radius is under 0.8× their neighbours’ are dropped. The op that turns a mark into a direction (marker_direction) assumed the ball sits on the optical axis, so for a ball crossing the frame the change of line of sight showed up as apparent spin (0.01 rad per ms, 7 % of one frame’s rotation) — given the camera K it now solves the perspective exactly. Honestly: synthetic video (known colour, no real lighting or blur); the aerodynamic coefficients (C_d = 0.4, spin-ratio C_L) are the same in truth and fit; spin is constant in flight. 8 gates, 34.4 s.
↑ Filming a Spinning Table-Tennis Ball and Reading Its Spin Two Ways — From the Curve and From the Markings ―― Two table-tennis balls launched at the same speed and angle land in different places if they spin differently: topspin dips and lands short, backspin floats and lands long, sidespin curves sideways (the Magnus effect, force along ω × v). This exhibit films four balls that differ only in spin (150 rad/s ≈ 1,430 rpm) with two 240 fps cameras and reads the spin in two ways. From the curve: fit the drag + Magnus equation of motion to the triangulated pre-bounce track with 9 parameters (position, velocity, spin) — the new op fit_spin, a damped least-squares fit. The perpendicular-spin error is 3.9 % (top), 0.9 % (back), 1.7 % (side), and the landing point predicted from the read spin is within 2 cm of the truth for all four (x = [0.488, 0.754, 1.124] m; top < none < back). From the markings: film the same ball with a 1000 fps close-up camera (20 frames) and fit the motion of the 14 black marks with Kabsch — 4.8 %, 3.2 %, 0.9 %. The two readings are independent (one sees only the track, the other only the marks) and agree within 10 % (a second-implementation gate). Theorem gate: Magnus force is ω × v, so spin parallel to the direction of travel produces no force — the instantaneous acceleration differs by less than 1e-12, and after 0.25 s of flight the track moves 7.1 mm (versus 5.4 cm for perpendicular spin). fit_spin therefore returns this component separately, and the 22 rad/s the fit reports for the no-spin ball lies almost entirely along that unreadable direction. With a short track the curve is buried in detection error and the spin cannot be read (400.3 % → 4.0 %). Found on the way: from the server-side camera the net’s white band hides the top half of a low ball beyond the net and shifts the detected centre (up to 26 mm after triangulation) — detections whose radius is under 0.8× their neighbours’ are dropped. The op that turns a mark into a direction (marker_direction) assumed the ball sits on the optical axis, so for a ball crossing the frame the change of line of sight showed up as apparent spin (0.01 rad per ms, 7 % of one frame’s rotation) — given the camera K it now solves the perspective exactly. Honestly: synthetic video (known colour, no real lighting or blur); the aerodynamic coefficients (C_d = 0.4, spin-ratio C_L) are the same in truth and fit; spin is constant in flight. 8 gates, 34.4 s.
↑ The measurement ―― 同じ速さ・同じ向き(v₀ = (6.0, 0, 1.3) m/s)で打ち出した 3 本を横から: トップスピン(橙)は沈んで x = 0.49 m、無回転(黄)は 0.75 m、バックスピン(青)は浮いて 1.12 m に落ちる(台の中心から)。球は見やすさのため 1.6 倍で描いた。MP4 = 240 fps の全コマ(1/8 スロー)。 (figure labels are in Japanese; the numbers are the same)
↑ 曲がり方(軌跡に運動方程式を当てる)と模様(近接カメラの Kabsch)は独立。
↑ 跳ねる前の先頭 n コマだけで回転を読む。
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_table_tennis_spin/02_spin_closeup.gif)
↑ The animation ―― 近接カメラ(1000 fps、20 コマ = 20 ms)のトップスピン。黒い模様(14 個、見えるのは 4〜6 個)を前のコマと向きで対応づけ、Kabsch で回転を当てる: ω = [-0.7, 150.1, -7.2] rad/s(真値 (0, 150, 0))。1/100 スロー、3 回繰り返し。
py -3.11 examples/poc_table_tennis_spin.py
Source: examples/poc_table_tennis_spin.py
This run produced 5 figures in total - see them all
Ops used (notes): add_ball · ball_detect · ball_mesh · ball_params · ball_set_pose · ball_track · bounce_detect · camera_rig · fit_spin · flight_ode · flight_simulate · impact_params · marker_direction · reproject · rotation_from_omega · spin_from_marker_sequence · table_params · table_world · text_box · track_triangulate · world_camera
| When a table-tennis ball bounces on the table, restitution e decides how much vertical speed survives and friction trades horizontal speed against spin. If the contact point slides only a little the sliding stops during the bounce and the ball leaves rolling; if it slides a lot it leaves still sliding. This exhibit films 22 bounces with one high-speed camera from the side (1000 fps, ROI readout) and reads e, the friction coefficient μ and the kind of bounce from the images alone, checking them against published values. The plane of motion is known, so one camera gives positions (the centre pixel’s line of sight meets that plane). The drag + Magnus equation of motion is fitted before and after the bounce to get the velocities at contact, and the spin is read from the ball’s marks in two stages (the new op spin_from_marker_sequence). Gates: the closed forms of Cross 2002 (thin shell: a bounce ending rolling has v_x’ = 0.6 v_x + 0.4 rω, one still sliding has Δv_t = μ(1+e) | v_z | , the boundary is (2/5) | s | = μ(1+e) | v_z | ) agree with the impulse implementation over 300 random impacts / the ITTF table bounce (Laws 2.1.3: 30 cm → about 23 cm) is 23.0 cm from the video / the slope of e against impact speed, -0.00572 per km/h, is within 1.4 % of the −0.0058 measured by Inaba et al. 2017 / sliding bounces give μ = 0.2500 (truth 0.25) / the kind of bounce agrees with the closed-form boundary (15 / 15) / bounces that end rolling have rω’ within 3 % of v_x’ / on a frictionless table neither horizontal speed nor spin changes. Found on the way: reading 30 → 23 cm as e = √(23/30) = 0.876 drops the air drag — that table bounces only 21.7 cm, and the e that gives about 23 cm with drag is 0.9019. The formula of Inaba et al. itself (intercept 1.0002) makes a table that bounces 25.5 cm from 30 cm, so the two published values disagree (lab table versus standard, or method, not checked). The apparent μ of a bounce that ends rolling is only a lower bound. Honestly: synthetic video; the table’s e(v) joins the ITTF intercept and the slope of Inaba et al.; μ is constant (their measurements rise with contact-point speed); the ball does not deform (buckling from 5.5 m/s). 7 gates, 21.6 s. |
| *↑ Filming a Bouncing Table-Tennis Ball with a High-Speed Camera and Reading Restitution and Friction — Checked Against Published Values ―― When a table-tennis ball bounces on the table, restitution e decides how much vertical speed survives and friction trades horizontal speed against spin. If the contact point slides only a little the sliding stops during the bounce and the ball leaves rolling; if it slides a lot it leaves still sliding. This exhibit films 22 bounces with one high-speed camera from the side (1000 fps, ROI readout) and reads e, the friction coefficient μ and the kind of bounce from the images alone, checking them against published values. The plane of motion is known, so one camera gives positions (the centre pixel’s line of sight meets that plane). The drag + Magnus equation of motion is fitted before and after the bounce to get the velocities at contact, and the spin is read from the ball’s marks in two stages (the new op spin_from_marker_sequence). Gates: the closed forms of Cross 2002 (thin shell: a bounce ending rolling has v_x’ = 0.6 v_x + 0.4 rω, one still sliding has Δv_t = μ(1+e) | v_z | , the boundary is (2/5) | s | = μ(1+e) | v_z | ) agree with the impulse implementation over 300 random impacts / the ITTF table bounce (Laws 2.1.3: 30 cm → about 23 cm) is 23.0 cm from the video / the slope of e against impact speed, -0.00572 per km/h, is within 1.4 % of the −0.0058 measured by Inaba et al. 2017 / sliding bounces give μ = 0.2500 (truth 0.25) / the kind of bounce agrees with the closed-form boundary (15 / 15) / bounces that end rolling have rω’ within 3 % of v_x’ / on a frictionless table neither horizontal speed nor spin changes. Found on the way: reading 30 → 23 cm as e = √(23/30) = 0.876 drops the air drag — that table bounces only 21.7 cm, and the e that gives about 23 cm with drag is 0.9019. The formula of Inaba et al. itself (intercept 1.0002) makes a table that bounces 25.5 cm from 30 cm, so the two published values disagree (lab table versus standard, or method, not checked). The apparent μ of a bounce that ends rolling is only a lower bound. Honestly: synthetic video; the table’s e(v) joins the ITTF intercept and the slope of Inaba et al.; μ is constant (their measurements rise with contact-point speed); the ball does not deform (buckling from 5.5 m/s). 7 gates, 21.6 s.* |
↑ The measurement ―― ITTF の台の跳ねの試験(Laws 2.1.3: 30 cm から落として約 23 cm)。球の下端を 30 cm から落とし、動画から読んだ跳ねの高さは 23.0 cm(世界の真値 23.0 cm)。物差しは 1 cm 刻み。MP4 = 240 fps の全コマ(1/8 スロー)。 (figure labels are in Japanese; the numbers are the same)
↑ 動画から読んだ e(22 本)と公表値。
↑ The animation ―― バックスピン(ω = −150 rad/s)の跳ね(当たる瞬間 v = (4.9, −4.3) m/s)、1000 fps を 1/40 スローで。接地点が大きく滑ったまま離れる(滑り +7.89 → +2.89 m/s)。模様から読んだ回転 -150 → +0 rad/s、横の速さの減り 1.997 m/s → 見かけの μ 0.250。
↑ The animation ―― トップスピン(ω = +200 rad/s)の跳ね(当たる瞬間 v = (2.9, −3.1) m/s)。接地点の滑りが小さいので跳ねの途中で止まり、転がりに移って離れる(滑り -1.17 → -0.01 m/s、跳ねた後の rω’ = 3.320 m/s と v_x’ = 3.312 m/s)。
py -3.11 examples/poc_table_tennis_bounce.py
Source: examples/poc_table_tennis_bounce.py
This run produced 5 figures in total - see them all
Ops used (notes): add_ball · ball_detect · ball_mesh · ball_params · ball_set_pose · bounce · bounce_detect · crosshair · flight_fit · flight_ode · flight_simulate · flight_state_at · impact_params · marker_direction · ray_plane_range · reproject · rotation_from_omega · spin_from_marker_sequence · table_params · table_world · text_box · world_camera
| A table-tennis robot reads where the ball is, then computes a return that lands on a chosen point of the opponent’s half. If the reading is off by δ, the shot is computed from the misread position but the ball leaves from the true one, so the landing point moves; past the margin to the edge the ball is out and the rally ends. This exhibit works out, before the shot, how a reading error becomes a landing error (the Jacobian J, 2 × 3) and checks whether noise, latency and broken rallies are explained by it. Gates: without air, the numerical J through aiming, racket planning, impact and flight differs from the closed form ΔL_xy = −δ_xy − (v_xy/ | v_z(T) | )δ_z by 2.5e-06 (a 1 cm height error moves the landing 2.1 cm along the table) / with drag and Magnus, the spread of 300 shots with σ = 2 cm readings (along 4.6, across 2.0 cm) is within 1.0 and 0.8 % of J Σ Jᵀ / aiming 6 cm from the edge with σ = 3 cm, the out probability is predicted 0.043 and 300 shots give 0.060 (1.5 σ binomial) / the landing error for latency τ = 5, 10, 20 ms matches J · (−vτ − ½gτ² ẑ) within 0.2–0.6 % / noise-free rallies reach the cap of 10 shots all 4 times, while σ = 6 cm (1.2 × the bound σ* = 5.2 cm from the margin) ends after 9, 8, 2, 7 shots, all out / with no error the ball lands 0.52 mm from the target. Found on the way: the height of the “reading τ old” was written with + ½gτ² (it is −). The gate puts the same reading error on both sides, so the mistake passed; a numerical integral caught it. With large noise (12 cm from the edge, σ = 5 cm) the prediction is 0.017 and the shots give 0.003; an earlier seed missed by 2.7 σ the other way, so 300 shots cannot tell which way the linearisation errs. Honestly: only the ball position is misread (velocity and spin are true), the noise is independent Gaussian per frame, the racket hits exactly as planned, and an out is judged only by the edge margin. 6 gates, 289 s. |
| *↑ Reading Errors End the Table-Tennis Rally — How Much Noise and Latency Move the Landing Point, in Closed Form Before the Shot ―― A table-tennis robot reads where the ball is, then computes a return that lands on a chosen point of the opponent’s half. If the reading is off by δ, the shot is computed from the misread position but the ball leaves from the true one, so the landing point moves; past the margin to the edge the ball is out and the rally ends. This exhibit works out, before the shot, how a reading error becomes a landing error (the Jacobian J, 2 × 3) and checks whether noise, latency and broken rallies are explained by it. Gates: without air, the numerical J through aiming, racket planning, impact and flight differs from the closed form ΔL_xy = −δ_xy − (v_xy/ | v_z(T) | )δ_z by 2.5e-06 (a 1 cm height error moves the landing 2.1 cm along the table) / with drag and Magnus, the spread of 300 shots with σ = 2 cm readings (along 4.6, across 2.0 cm) is within 1.0 and 0.8 % of J Σ Jᵀ / aiming 6 cm from the edge with σ = 3 cm, the out probability is predicted 0.043 and 300 shots give 0.060 (1.5 σ binomial) / the landing error for latency τ = 5, 10, 20 ms matches J · (−vτ − ½gτ² ẑ) within 0.2–0.6 % / noise-free rallies reach the cap of 10 shots all 4 times, while σ = 6 cm (1.2 × the bound σ* = 5.2 cm from the margin) ends after 9, 8, 2, 7 shots, all out / with no error the ball lands 0.52 mm from the target. Found on the way: the height of the “reading τ old” was written with + ½gτ² (it is −). The gate puts the same reading error on both sides, so the mistake passed; a numerical integral caught it. With large noise (12 cm from the edge, σ = 5 cm) the prediction is 0.017 and the shots give 0.003; an earlier seed missed by 2.7 σ the other way, so 300 shots cannot tell which way the linearisation errs. Honestly: only the ball position is misread (velocity and spin are true), the noise is independent Gaussian per frame, the racket hits exactly as planned, and an out is judged only by the edge margin. 6 gates, 289 s.* |
↑ The measurement ―― 高さを 5 cm 高く読むと、狙いの計算(灰)は低い弾道を選び、本当の位置から打った球(赤)は狙いより 10.2 cm 手前に落ちる。J の前後の増幅 2.06 × 5 cm = 10.3 cm(一次の予測)。 (figure labels are in Japanese; the numbers are the same)
↑ 読みの雑音は 3 方向に同じ大きさでも、着地点のずれは前後に 2.2 倍伸びる(高さの読み違いが落ちる角の分だけ増える)。
↑ The animation ―― 上から見た送り合い(1/2 速)。上 = 読みの雑音 0 で上限の 10 本、下 = 毎コマの読みに σ = 6 cm で 9 本(アウト)。ラケットは届いている —— 途切れる理由は空振りでなく、読み違いから狙った打球のアウト。
py -3.11 examples/poc_table_tennis_rally_loop.py
Source: examples/poc_table_tennis_rally_loop.py
This run produced 4 figures in total - see them all
Ops used (notes): aim_velocity · ball_params · flight_simulate · impact_params · racket_impact · racket_params · racket_plan · rally_simulate · table_params · text_box
Periodic material carries an exact invariant that any temporal operator must satisfy: for a video of period T, an operator that handles the periodic boundary correctly obeys op(roll(v,k)) == roll(op(v),k) exactly, and any implementation that pads the ends by replication, clamping or truncation breaks it. Because perpetual_loop makes every time-dependent quantity a function of theta, the seam is never created rather than removed (seam ratio 1.0509, where 1 and not 0 is the correct answer), so the truth for that equality is exactly zero difference. No new operator was added. The core finding is that a perfect score came out of an empty output: run with its defaults, three_frame_difference reports a maximum difference of 0.0e+00 and zero contaminated frames, yet only 0 of 32 frames carry any content - the default threshold of 0.1 exceeds the material’s largest frame-to-frame change of 0.0841, so nothing is detected at all, and an empty output shifts to another empty output, agreeing trivially. Lowering the threshold to 0.03 gives the same operator 30 of 32 frames of content, 4 contaminated frames and a maximum difference of 1.000 - the perfect score was a lie. Scanning all sixteen single-input video-to-video operators in the ledger splits them into three groups: windowed operators contaminate only the ends (2 frames for causal frame differencing and optical-flow magnitude, 4 for the moving average, 8 for windowed background subtraction, temporal bilateral and temporal median), so trimming that many frames leaves the rest exactly equivariant; recursive or globally normalising operators contaminate all 32 frames and trimming does not help; and three operators return almost nothing, which reads as zero contamination but is not a pass. The contaminated frames are predictable not merely in count but as a set: a window of width w contaminates roll(B,k) union B, and across w = 3, 5, 7, 9, 11 and 13 the predicted set matched exactly (4, 8, 12, 16, 19, 21). The naive prediction of w-1 frames fails in all six cases and 2(w-1) fails at w = 11, where the two bands overlap by exactly one frame at T = 32 with a shift of 9. The first prediction assumed a centred window and was wrong; subtracting the mismatched sets showed contamination only at the head with the tail untouched, which means the window is trailing rather than centred - the orientation of a window can be read off the shape of the contaminated set without reading the implementation.
↑ Testing the Periodic Boundary of Temporal Operators with a Seamless Loop ―― Periodic material carries an exact invariant that any temporal operator must satisfy: for a video of period T, an operator that handles the periodic boundary correctly obeys op(roll(v,k)) == roll(op(v),k) exactly, and any implementation that pads the ends by replication, clamping or truncation breaks it. Because perpetual_loop makes every time-dependent quantity a function of theta, the seam is never created rather than removed (seam ratio 1.0509, where 1 and not 0 is the correct answer), so the truth for that equality is exactly zero difference. No new operator was added. The core finding is that a perfect score came out of an empty output: run with its defaults, three_frame_difference reports a maximum difference of 0.0e+00 and zero contaminated frames, yet only 0 of 32 frames carry any content - the default threshold of 0.1 exceeds the material’s largest frame-to-frame change of 0.0841, so nothing is detected at all, and an empty output shifts to another empty output, agreeing trivially. Lowering the threshold to 0.03 gives the same operator 30 of 32 frames of content, 4 contaminated frames and a maximum difference of 1.000 - the perfect score was a lie. Scanning all sixteen single-input video-to-video operators in the ledger splits them into three groups: windowed operators contaminate only the ends (2 frames for causal frame differencing and optical-flow magnitude, 4 for the moving average, 8 for windowed background subtraction, temporal bilateral and temporal median), so trimming that many frames leaves the rest exactly equivariant; recursive or globally normalising operators contaminate all 32 frames and trimming does not help; and three operators return almost nothing, which reads as zero contamination but is not a pass. The contaminated frames are predictable not merely in count but as a set: a window of width w contaminates roll(B,k) union B, and across w = 3, 5, 7, 9, 11 and 13 the predicted set matched exactly (4, 8, 12, 16, 19, 21). The naive prediction of w-1 frames fails in all six cases and 2(w-1) fails at w = 11, where the two bands overlap by exactly one frame at T = 32 with a shift of 9. The first prediction assumed a centred window and was wrong; subtracting the mismatched sets showed contamination only at the head with the tail untouched, which means the window is trailing rather than centred - the orientation of a window can be read off the shape of the contaminated set without reading the implementation.
↑ The measurement ―― perpetual_loop("plasma_orbit") の 4 コマ。時間に依る量をすべて θ の関数にしてあるので、t = T は t = 0 と同じ式です —- 継ぎ目は消したのではなく最初から作られていません(継ぎ目の比 1.0509、0 ではなく 1 が正解)。★だから「巡回シフトしても同じ動画」という厳密な真値が素材の側に立ちます。 (figure labels are in Japanese; the numbers are the same)
↑ three_frame_difference の同じコマです。
↑ 継ぎ目の無い動画 32 コマを巡回シフトして測った全数走査です。
↑ 窓幅 7 のときに汚れたフレームの位置。
↑ 端だけが汚れる 6 本について、汚れたフレーム数を少ない順に並べたもの。
↑ The animation ―― 同じ動画を実際に回したもの(32 コマ)。最後のコマから最初のコマへ戻るところに継ぎ目が見えません —- 消したのではなく、時間に依る量をすべて θ の関数にしてあるので最初から存在しないからです。★この性質があるので「巡回シフトしても同じ動画」が厳密な真値になり、時間方向 op の周期境界を数で採点できます。
py -3.11 examples/poc_periodic_video_boundary.py
Source: examples/poc_periodic_video_boundary.py
This run produced 9 figures in total - see them all
Ops used (notes): moving_average_window · perpetual_loop · perpetual_loop_seam · three_frame_difference
Reprojection error in camera calibration, seam mismatch in a panorama, residual in point-cloud registration: all are read as ‘smaller is better’. The 19 exhibits here, with ground truth in hand, show where that reading fails.
Reprojection RMS of 0.0688–0.0690 px alongside focal-length errors of 0.026–7.334 %. Adjacent seams at 0.12 px while the single closing seam opens by 1.5 px. Spheres and cylinders converging to the same residual with an arbitrary pose. Least squares drives the residual down to the noise; whether it lands on the truth is a separate question.
A trap in the measuring procedure itself is kept on display: fix one point cloud and sweep only the pose, and you still have a sample of one — the random seed alone produced both ‘0 % quadrant errors’ and ‘100 %’. About the only absolute quantity measurable without ground truth is the loop-closure error of a full 360-degree sweep.
A small reprojection residual does not ensure correct camera focal length. Grid points from known poses were recalibrated, yielding 7.334 % focal-length error for shallow board tilt. The residual barely changes while internal parameters collapse, yet optimization still returns a plausible answer.
↑ A Reprojection Error of 0.05 px Guarantees Nothing ―― Grid points projected with known intrinsics and poses, re-calibrated, and the error split by component. With the board tilted 32 / 8 / 2 degrees the reprojection RMS spans 0.0688 to 0.0690 px (ratio 1.00) while the focal-length error spans 0.026 to 7.334 % (281x). With a distorting camera the degeneracy gate never fires, and the non-linear optimiser returns an answer even for a fronto-parallel board.
↑ The measurement ―― RMS は 1.00 倍しか動かないのに fx 誤差は 281 倍動く。配置の良し悪しを映すのは sigma_fx のほう。 (figure labels are in Japanese; the numbers are the same)
↑ 2 本は重なる。
↑ どちらも雑音 0 では真値。
↑ The animation ―― 第 1 部: 同じ雑音 0.05 px の観測を、傾き 32 度(左)と 2 度(右)の配置で解く反復。再投影 RMS はどちらも 0.069 / 0.069 px まで下がるが、fx は左が 1199.7(誤差 0.026 %)、右が 1112.0(誤差 7.33 %)で止まる。第 2 部: fx を真値の 0.92〜1.08 倍に固定して残りを解き直すと、左は RMS が 0.54 / 0.45 px(両端)まで跳ね上がるのに、右は 0.069 / 0.069 px とほとんど動かない —— 板までの距離 Z が fx と同じ比で動いて(Z 比 0.920 / 1.080)、画素の位置を保つため。右の配置では再投影誤差が焦点距離について何も言っていない。
py -3.11 examples/poc_camera_calibration.py
Source: examples/poc_camera_calibration.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · camera_calibration · data_to_pixel · plot_series · project_points · reprojection_error · text_box · ticks
36 frames cut from a cylindrical panorama by a known rotation sequence and chained pairwise around a full turn. Adjacent seams agree to 0.12 px, yet the one closing seam opens by 1.5 px (13x). The buried bundle_adjust_mosaic did not beat the chain (30 of 36 frames were left as the identity); the worst pose error goes from 1.65 px for the chain to 0.56 px with global optimisation.
↑ Chain the Neighbours Together and You Cannot Get Back Where You Started ―― 36 frames cut from a cylindrical panorama by a known rotation sequence and chained pairwise around a full turn. Adjacent seams agree to 0.12 px, yet the one closing seam opens by 1.5 px (13x). The buried bundle_adjust_mosaic did not beat the chain (30 of 36 frames were left as the identity); the worst pose error goes from 1.65 px for the chain to 0.56 px with global optimisation.
↑ The measurement ―― 系 2(等分)は閉ループ誤差を下げるのに姿勢はかえって悪化する。新しい観測を足さずに効くのは系 3。 (figure labels are in Japanese; the numbers are the same)
↑ 上 2 枚はどちらも継ぎ目が綺麗に見える(後勝ちの上書きで混合しないため)。
↑ 実測 log-log 傾き 0.89。
↑ The animation ―― 鎖(隣どうしの相対回転を掛けるだけ)で 36 枚を円筒に 1 枚ずつ貼る過程。白い枠が真の位置、橙の枠が鎖の推定位置(ずれを 20 倍に誇張)。隣どうしの継ぎ目は平均 0.119 px で合っているのに、真の姿勢からのずれ(右下の曲線)は積み上がって最悪 1.65 px(フレーム 16)。一周して 0 枚目(赤紫)と35 枚目(緑)を重ねると閉じる継ぎ目が 1.50 px 開き、縁に色の縞(二重像)が出る(左下、4 倍拡大)。最後に同じ 36 本の辺を閉ループ拘束で解き直すと(水色)、姿勢のずれは最悪 1.18 px、閉じる継ぎ目は 0.11 px。
py -3.11 examples/poc_panorama_drift.py
Source: examples/poc_panorama_drift.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · hom_vector_to_proj_hom_mat2d · plot_series · pose_error · text_box · ticks · warp_by_plane
The first exhibit in this museum measured on a real photograph (Middlebury 2014 motorcycle, with ground-truth disparity). The distributor’s own note says the holes in the truth are NaN; they are +inf, so np.nanmedian reports a median disparity of 44.97 px instead of 42.55 px, while fill_disparity and apply_cmap — which test with isfinite — handle them correctly. The default max_disp=16 scores bad2 95.06 %: any search range below the true maximum of 59.91 px loses the whole foreground, so the cliff sits between 48 and 64, which geometry predicts before any measurement. Against a 94.04 % null, SGM scores 15.81 %, and discarding the least confident 40 % takes it to 6.80 %. Converting to metric depth bends the scene: depth_from_disparity had no principal-point offset, and ignoring the real doffs of 31.086 px puts points 1.519 to 5.243 times too far — no single scale repairs it, since the best factor of 0.3733 still leaves 958.3 mm RMS over a 2889 mm range, pushing far surfaces +1676 mm out and pulling near ones -926 mm in. The argument was added here, and it reproduces the closed form exactly.
↑ Measuring on a Real Stereo Photograph — Three Stumbles Synthesis Never Produces ―― The first exhibit in this museum measured on a real photograph (Middlebury 2014 motorcycle, with ground-truth disparity). The distributor’s own note says the holes in the truth are NaN; they are +inf, so np.nanmedian reports a median disparity of 44.97 px instead of 42.55 px, while fill_disparity and apply_cmap — which test with isfinite — handle them correctly. The default max_disp=16 scores bad2 95.06 %: any search range below the true maximum of 59.91 px loses the whole foreground, so the cliff sits between 48 and 64, which geometry predicts before any measurement. Against a 94.04 % null, SGM scores 15.81 %, and discarding the least confident 40 % takes it to 6.80 %. Converting to metric depth bends the scene: depth_from_disparity had no principal-point offset, and ignoring the real doffs of 31.086 px puts points 1.519 to 5.243 times too far — no single scale repairs it, since the best factor of 0.3733 still leaves 958.3 mm RMS over a 2889 mm range, pushing far surfaces +1676 mm out and pulling near ones -926 mm in. The argument was added here, and it reproduces the closed form exactly.
↑ The measurement ―― 下位 4 割を捨てると bad2 は 26.75 % -> 6.80 %。 (figure labels are in Japanese; the numbers are the same)
↑ 真の最大視差を下回る設定は前景を丸ごと失う。
↑ 単一のスケールでは直らない(最良でも残差 958 mm RMS)。
↑ census が最下位なのは窓が 64 bit で頭打ちだから。
py -3.11 examples/poc_real_stereo_depth.py
Source: examples/poc_real_stereo_depth.py
This run produced 5 figures in total - see them all
ICP (iterative closest point) success rate contoured against the initial pose error, with the point cloud re-sampled on every trial. With no translation offset, success drops below 50 % at 90 degrees for point-to-point and 120 degrees for point-to-plane. Spheres and cylinders converge to the same residual with an arbitrary pose; with the global method all 16 of 16 trials appear converged and the pose is wrong — undetectable from the residual.
↑ The Convergence Basin of Point-Cloud Registration — How Far Off Can the Initial Pose Be? ―― ICP (iterative closest point) success rate contoured against the initial pose error, with the point cloud re-sampled on every trial. With no translation offset, success drops below 50 % at 90 degrees for point-to-point and 120 degrees for point-to-plane. Spheres and cylinders converge to the same residual with an arbitrary pose; with the global method all 16 of 16 trials appear converged and the pose is wrong — undetectable from the residual.
Terms: ICP — A registration method that alternates nearest-point correspondence with rigid-transform estimation between two point clouds.
↑ The measurement ―― 非対称性が消えると 4 候補が形として区別できず、選択が崩れる(選ばれた解が第 1 候補から離れる)。 (figure labels are in Japanese; the numbers are the same)
↑ 平らな線ほど「広い」。
↑ 球・円柱は残差が小さいまま姿勢が任意。
↑ The animation ―― 点対点 ICP を 1 反復ずつ動かす(fs.icp(max_iter=1) を前回の姿勢から連鎖)。灰 = 目標の点群、色 = 動かしている点群(斜めから見た正射影)。同じ回転軸で初期回転ずれだけを 30 / 90 / 150 度と変え、並進ずれは直径の 10 %。反復予算は本文の点対点 ICP と同じ 60 回で、その後の回転誤差: 30 度 → 0.6 度(成功)、90 度 → 0.6 度(成功)、150 度 → 179.5 度(失敗)。下の曲線は回転誤差の推移(対数、灰線 = 成功のしきい値 3 度)。動画専用に取り直した 1 組の点群での 1 試行の軌跡で、成功率は第 2 章の表。
py -3.11 examples/poc_registration_basin.py
Source: examples/poc_registration_basin.py
This run produced 5 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · estimate_normals · farthest_point_sampling · plot_series · text_box · ticks
Shape features are routinely called rotation-invariant, but the pixel grid is not invariant under rotation, so the claim usually breaks at a helper — how the boundary is counted, how the image is interpolated, where the threshold lands. A real photograph (scikit-image coins, Greek coins from Pompeii, British Museum) is turned through a full circle in 5-degree steps and the wobble is split into three arms: A rotate the greyscale and re-threshold (what people actually do), B rotate the 0-degree binary mask with nearest neighbour (re-rasterisation only), C multiples of 90 degrees via np.rot90 (exact). The rotation itself is done with scipy, not with fullseye — measuring your own invariance with your own rotation hides errors that lean the same way. Arm C is exactly 0.00 % for all seven quantities: the measurements carry no orientation bias at all, so every bit of the wobble is the cost of re-laying the boundary on the grid. The prediction was wrong: rotating the binary mask turns out to be far worse than interpolating the grey — perimeter 2.52 % (A) vs 9.47 % (B), circularity 4.82 % vs 20.45 %. Nearest-neighbour rotation re-lays a staircase; interpolating the grey re-derives the boundary from the underlying continuous signal. Rotate the greyscale, never the mask. A synthetic square anchors the closed form: the 4-connected staircase perimeter of a square rotated 45 degrees is bounded by 4L → 4L√2 (+41.4 %), but the measured swing is 7.91 % because regionprops applies a Crofton-style correction — the closed form is a ceiling, not a prediction. Finally, check the denominator: Hu[1] is ~0 for a near-circular blob (3.9e-04), so its 29.6 % relative spread means nothing at all.
↑ Which Quantities Really Survive a Rotation — Auditing Invariance on a Real Coin ―― Shape features are routinely called rotation-invariant, but the pixel grid is not invariant under rotation, so the claim usually breaks at a helper — how the boundary is counted, how the image is interpolated, where the threshold lands. A real photograph (scikit-image coins, Greek coins from Pompeii, British Museum) is turned through a full circle in 5-degree steps and the wobble is split into three arms: A rotate the greyscale and re-threshold (what people actually do), B rotate the 0-degree binary mask with nearest neighbour (re-rasterisation only), C multiples of 90 degrees via np.rot90 (exact). The rotation itself is done with scipy, not with fullseye — measuring your own invariance with your own rotation hides errors that lean the same way. Arm C is exactly 0.00 % for all seven quantities: the measurements carry no orientation bias at all, so every bit of the wobble is the cost of re-laying the boundary on the grid. The prediction was wrong: rotating the *binary mask turns out to be far worse than interpolating the grey — perimeter 2.52 % (A) vs 9.47 % (B), circularity 4.82 % vs 20.45 %. Nearest-neighbour rotation re-lays a staircase; interpolating the grey re-derives the boundary from the underlying continuous signal. Rotate the greyscale, never the mask. A synthetic square anchors the closed form: the 4-connected staircase perimeter of a square rotated 45 degrees is bounded by 4L → 4L√2 (+41.4 %), but the measured swing is 7.91 % because regionprops applies a Crofton-style correction — the closed form is a ceiling, not a prediction. Finally, check the denominator: Hu[1] is ~0 for a near-circular blob (3.9e-04), so its 29.6 % relative spread means nothing at all.*
↑ The measurement ―― 実写の硬貨を 5 度ずつ 1 周。地がモノクロなので輪郭は彩度のある色で描いている(灰色には彩度が無いので、どの階調とも色相で区別がつく)。右の表の数字がどれだけ揺れるかが見える。 (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_rotation_invariance_audit.py
Source: examples/poc_rotation_invariance_audit.py
This run produced 2 figures in total - see them all
Ops used (notes): annotate_outline · annotate_table · blob_features · blob_label · circularity · eccentricity · moments_region_2nd_invar · moments_region_central_invar · text_box
The author’s wish: “put the finished form of everything we built into the most realistic environment there is” — so, both environments. Our own world (all ground truth, not photoreal) and the external photoreal simulator CARLA 0.9.16 (code MIT, assets CC-BY; photoreal, but its formulas are hidden) are joined by a bridge, carlabridge (25 ops, numpy only). On a straight in Town04 a lead car is placed 8–64 m ahead and RGB, depth and semantic images are captured; the recorded poses (left-handed, degrees) are mapped to our right-handed frame and the image-plane distance to the rear face is computed in closed form; the same rule-based perception (median depth over car-labelled pixels) is applied to CARLA’s image and to our own world re-rendered with the same intrinsics and mounting. CARLA’s depth sits +0.10 m from the truth in every frame (bounding-box origin offset), our re-render within ±0.3 m, the car pixel count follows 1/d² in both (log-log slopes −1.94 / −1.99) with pixel ratios 0.90–1.08. Gates: rotation matrices equal CARLA’s own get_matrix, camera pose equals look_at, depth round-trips within one quantisation step, world → record → re-render is pixel-identical. Honestly: CARLA’s truth itself cannot be verified — what is verified is that the conventions are invertible and agree with the closed form; agreement is not proof of correctness. CARLA imagery uses CC-BY assets (CARLA team).
↑ One Scene, Two Worlds — Scoring a Photoreal Simulator’s Depth Against a Closed-Form Truth, Side by Side with Our Own World ―― The author’s wish: “put the finished form of everything we built into the most realistic environment there is” — so, both environments. Our own world (all ground truth, not photoreal) and the external photoreal simulator CARLA 0.9.16 (code MIT, assets CC-BY; photoreal, but its formulas are hidden) are joined by a bridge, carlabridge (25 ops, numpy only). On a straight in Town04 a lead car is placed 8–64 m ahead and RGB, depth and semantic images are captured; the recorded poses (left-handed, degrees) are mapped to our right-handed frame and the image-plane distance to the rear face is computed in closed form; the same rule-based perception (median depth over car-labelled pixels) is applied to CARLA’s image and to our own world re-rendered with the same intrinsics and mounting. CARLA’s depth sits +0.10 m from the truth in every frame (bounding-box origin offset), our re-render within ±0.3 m, the car pixel count follows 1/d² in both (log-log slopes −1.94 / −1.99) with pixel ratios 0.90–1.08. Gates: rotation matrices equal CARLA’s own get_matrix, camera pose equals look_at, depth round-trips within one quantisation step, world → record → re-render is pixel-identical. Honestly: CARLA’s truth itself cannot be verified — what is verified is that the conventions are invertible and agree with the closed form; agreement is not proof of correctness. CARLA imagery uses CC-BY assets (CARLA team).
↑ The measurement ―― 先行車の後ろ面までの像面距離の真値は記録の姿勢から閉形式で出す。車のラベルの画素の深度の中央値は CARLA で全コマ +0.1 m、自前の描き直しで ±0.3 m。最下行から平らな路面の式で出す距離は後輪の接地を見るのでバンパーより 0.5 m ほど先を指す。 (figure labels are in Japanese; the numbers are the same)
↑ 像面に平行な後ろ面の面積は距離の 2 乗に反比例する。
↑ コマごとの数字。
py -3.11 examples/poc_carla_bridge.py
Source: examples/poc_carla_bridge.py
This run produced 4 figures in total - see them all
Ops used (notes): camera_pose_to_carla · carla_camera_pose · carla_depth_decode · carla_depth_encode · carla_label_map · carla_label_unmap · carla_labels · carla_rotation_angles · carla_scene_load · carla_scene_save · carla_scene_synthetic · carla_transform_matrix · intrinsics_to_fullseye · lead_truth_depth · scene_pair_table
| The foundation for the author’s plan “one capstone per PoC series, driving first”. The intersection, railway crossing, slope and parallel-parking bay that earlier PoCs placed and scored one at a time are joined into a single road by the new module drivetown (10 ops): element k+1’s entry is placed onto element k’s exit in closed form with a 0.05 m overlap. The default town has 9 elements over 235.1 m. A longitudinal-only driver follows the centreline, treats the next stop line as a stationary lead vehicle for IDM braking, waits 2 s at the intersection and looks left and right at the crossing before moving on, and if the warning is active it waits until the barrier is up (the crossing carries gated warning posts, booms and a train driven by the interpretation-standard state machine). The rule pack town_rules gives JP = left-hand traffic with a mandatory stop and look at crossings, US and DE = right-hand traffic stopping only while the warning is active; packs other than JP are marked verified False because their primary sources have not been checked. Figures: the town from above (polygons, centreline, stop lines, stopping positions, signals), speed and acceleration over time, an on-board drive-through (stopped at red → moving on green → booms down → train passing → booms up and moving → before the slope), and a table of 9 rule-pack runs, and the 159-scene curriculum ledger. 38 gates: with a train warning at 20 s the car leaves the crossing at 65.50 s ≥ booms fully up at 65.45 s with zero time on the crossing while forbidden, JP stops twice while US/DE stop once without a train, the right-hand stop line mirrors about the intersection centre (s 40.95 ↔ 65.95), the boom covers 1078 pixels and the train 46,129 pixels in the on-board frames, joint position error minus overlap 1.1e-14 at all 8 joints with zero heading error, total length equals Σ centerline_length − 0.05 × 8 exactly, stop-line positions in closed form from the regulation dimensions, stops 0.537 / 0.536 m short of the lines, | a | at most 1.877 ≤ 3, trapezoidal ∫v dt matches s to 3.9e-11 m, braking distance ≥ v²/2b, the existing crossing_stop_check passes, and the same commands fed to long_simulate (RK4) stop within 0.002 m. Honestly: lateral motion is pinned to the centreline, the signal is “green after 2 s” without reading the lamp, there are no other vehicles or pedestrians, and the US/DE hold times reuse the JP values. |
| *↑ Assembling One Town — Auto-Joining Driving-School Elements, Driving Through from Entry to Exit, and Scoring Every Stop Line on the Way ―― The foundation for the author’s plan “one capstone per PoC series, driving first”. The intersection, railway crossing, slope and parallel-parking bay that earlier PoCs placed and scored one at a time are joined into a single road by the new module drivetown (10 ops): element k+1’s entry is placed onto element k’s exit in closed form with a 0.05 m overlap. The default town has 9 elements over 235.1 m. A longitudinal-only driver follows the centreline, treats the next stop line as a stationary lead vehicle for IDM braking, waits 2 s at the intersection and looks left and right at the crossing before moving on, and if the warning is active it waits until the barrier is up (the crossing carries gated warning posts, booms and a train driven by the interpretation-standard state machine). The rule pack town_rules gives JP = left-hand traffic with a mandatory stop and look at crossings, US and DE = right-hand traffic stopping only while the warning is active; packs other than JP are marked verified False because their primary sources have not been checked. Figures: the town from above (polygons, centreline, stop lines, stopping positions, signals), speed and acceleration over time, an on-board drive-through (stopped at red → moving on green → booms down → train passing → booms up and moving → before the slope), and a table of 9 rule-pack runs, and the 159-scene curriculum ledger. 38 gates: with a train warning at 20 s the car leaves the crossing at 65.50 s ≥ booms fully up at 65.45 s with zero time on the crossing while forbidden, JP stops twice while US/DE stop once without a train, the right-hand stop line mirrors about the intersection centre (s 40.95 ↔ 65.95), the boom covers 1078 pixels and the train 46,129 pixels in the on-board frames, joint position error minus overlap 1.1e-14 at all 8 joints with zero heading error, total length equals Σ centerline_length − 0.05 × 8 exactly, stop-line positions in closed form from the regulation dimensions, stops 0.537 / 0.536 m short of the lines, | a | at most 1.877 ≤ 3, trapezoidal ∫v dt matches s to 3.9e-11 m, braking distance ≥ v²/2b, the existing crossing_stop_check passes, and the same commands fed to long_simulate (RK4) stop within 0.002 m. Honestly: lateral motion is pinned to the centreline, the signal is “green after 2 s” without reading the lamp, there are no other vehicles or pedestrians, and the US/DE hold times reuse the JP values.* |
| *↑ The measurement ―― IDM(a_max 1.5、b_max 3.0、v_max 8)で停止線の手前に止まる。交差点は 2 秒。踏切は左右確認 4 秒の後、警報 20 s に始まった状態機械(降下 → 遮断 → 列車 → 上昇)が idle に戻る 65.5 s まで待って発進。下の帯 = 警報中の区間と列車が踏切に居る区間。 | a | の最大 1.88 m/s²。 (figure labels are in Japanese; the numbers are the same)* |
↑ 止まった位置は停止線の 0.54, 0.54 m 手前。
↑ GIF の 6 コマ(静止画)。
↑ JP = 左側通行・踏切は常に停止 + 左右確認(道路交通法 33 条 1 項、本文で確認)。
↑ docs/drive/kyosoku_scenarios.json の件数。
↑ The animation ―― 車載カメラ(640×400、13 コマ)で町を通し走行。赤信号で止まり、青で発進、踏切で止まって左右を見て、下りた遮断かんと点滅する警報灯の前で列車が過ぎて上がるまで待ってから渡り、坂を越えて縦列駐車の前を抜ける。
py -3.11 examples/poc_driving_town.py
Source: examples/poc_driving_town.py
This run produced 7 figures in total - see them all
Ops used (notes): course_crank · course_loop_bend · course_road · course_s_curve · crossing_stop_check · idm_accel · intersection · kyosoku_summary · long_params · long_simulate · town_centerline · town_chain · town_checks · town_crossing_state · town_layout · town_rules · town_run · town_stop_lines · town_world · world_camera
The author’s wish: “if possible, do it on a Japanese map”. The bundled maps of external photoreal simulators are European or American towns with right-hand traffic, and adding a Japanese town would need an editor build and Japanese assets, so we build Japan in our own world instead. The road network is an OpenStreetMap extract (equirectangular projection; widths from OSM tags or the Road Structure Ordinance lane widths as defaults). The road shape is the raster union of edges drawn as width-carrying segments, traced into boundary loops by Fullseye’s own contour tracer (contours_xld, whose area equals the pixel count exactly); the holes are city blocks, given pavements and kerbs. Buildings come from MLIT’s Project PLATEAU CityGML (LOD1 footprint and height → extruded prisms, driveplateau). The street furniture follows the regulation dimensions: the stop sign (330-A, 80 cm side), the stop line (45 cm), the road lettering 止まれ (Traffic Regulation Standard figure (1): 240 × 80 cm per character, 1 m apart, vertical), Japanese crosswalks with 45 cm stripes parallel to travel, crossbucks, utility poles and curve mirrors. The route is the shortest path (respecting one-way streets) shifted to the left-hand lane centre, with signals, stop signs and level crossings as stop lines, driven by drivetown’s through-run under the JP rule pack. Figures: Ginza 0.7 × 0.6 km from above, a bird’s-eye view with 852 PLATEAU buildings, an on-board drive-through (stop at red → go on green → stop sign), the same run’s frames, the furniture before a stop sign with its label image, the road-network table and the speed profile. 24 gates: 0.001° of latitude = 111.3195 m and agreement with a second implementation to 1e-9, straight-road union area = L w + π(w/2)² within 0.04 %, loop area = pixel count × step² within 1.5e-11 m², 4 holes in the 3 × 3 grid, shortest path 160 m with stop lines at 76.25 / 156.25 m in closed form, right-hand route mirrored, a 320 m detour around a reversed one-way street, two stops 0.54 m short of their lines with town_checks ok, the stop-sign plate 0.8 m on a side with its top at 2.5 m, the 止まれ column 9.2 m long, sign and lettering visible in the frame before the stop sign, and a 30 m synthetic CityGML prism. On real data: 586 OSM edges, 122 blocks, 852 buildings, area gate 5e-8 m², and a 461 m route to a signalled junction driven with 7 stops, ok. Honestly: 60 % of widths are class defaults where OSM has no tag; no lane assignment, signal phases, other vehicles or pedestrians; the 止まれ glyphs are polyline approximations (official dimensions); crossbuck plate sizes are assumed; slivers under 4 m² are not built; PLATEAU ground relief is ignored. Figure credits: roads © OpenStreetMap contributors (ODbL), buildings: MLIT Project PLATEAU. The data themselves are not committed.
↑ Building a Real Japanese Town in Our Own World and Driving It — Roads from OpenStreetMap, Buildings from PLATEAU, Japanese Furniture and Rules ―― The author’s wish: “if possible, do it on a Japanese map”. The bundled maps of external photoreal simulators are European or American towns with right-hand traffic, and adding a Japanese town would need an editor build and Japanese assets, so we build Japan in our own world instead. The road network is an OpenStreetMap extract (equirectangular projection; widths from OSM tags or the Road Structure Ordinance lane widths as defaults). The road shape is the raster union of edges drawn as width-carrying segments, traced into boundary loops by Fullseye’s own contour tracer (contours_xld, whose area equals the pixel count exactly); the holes are city blocks, given pavements and kerbs. Buildings come from MLIT’s Project PLATEAU CityGML (LOD1 footprint and height → extruded prisms, driveplateau). The street furniture follows the regulation dimensions: the stop sign (330-A, 80 cm side), the stop line (45 cm), the road lettering 止まれ (Traffic Regulation Standard figure (1): 240 × 80 cm per character, 1 m apart, vertical), Japanese crosswalks with 45 cm stripes parallel to travel, crossbucks, utility poles and curve mirrors. The route is the shortest path (respecting one-way streets) shifted to the left-hand lane centre, with signals, stop signs and level crossings as stop lines, driven by drivetown’s through-run under the JP rule pack. Figures: Ginza 0.7 × 0.6 km from above, a bird’s-eye view with 852 PLATEAU buildings, an on-board drive-through (stop at red → go on green → stop sign), the same run’s frames, the furniture before a stop sign with its label image, the road-network table and the speed profile. 24 gates: 0.001° of latitude = 111.3195 m and agreement with a second implementation to 1e-9, straight-road union area = L w + π(w/2)² within 0.04 %, loop area = pixel count × step² within 1.5e-11 m², 4 holes in the 3 × 3 grid, shortest path 160 m with stop lines at 76.25 / 156.25 m in closed form, right-hand route mirrored, a 320 m detour around a reversed one-way street, two stops 0.54 m short of their lines with town_checks ok, the stop-sign plate 0.8 m on a side with its top at 2.5 m, the 止まれ column 9.2 m long, sign and lettering visible in the frame before the stop sign, and a 30 m synthetic CityGML prism. On real data: 586 OSM edges, 122 blocks, 852 buildings, area gate 5e-8 m², and a 461 m route to a signalled junction driven with 7 stops, ok. Honestly: 60 % of widths are class defaults where OSM has no tag; no lane assignment, signal phases, other vehicles or pedestrians; the 止まれ glyphs are polyline approximations (official dimensions); crossbuck plate sizes are assumed; slivers under 4 m² are not built; PLATEAU ground relief is ignored. Figure credits: roads © OpenStreetMap contributors (ODbL), buildings: MLIT Project PLATEAU. The data themselves are not committed.
↑ The measurement ―― 銀座 0.7 × 0.6 km の俯瞰: OSM の道路網を幅つきでラスタ化した和集合の穴 = 街区(歩道 + 縁石)、信号・横断歩道・電柱。 道路: © OpenStreetMap contributors (ODbL) / 建物: 出典 国土交通省 Project PLATEAU (figure labels are in Japanese; the numbers are the same)
↑ PLATEAU(国交省 3D 都市モデル、LOD1 の足元と高さ)の建物 852 棟を押し出し柱で立てた銀座。
↑ 日本の道具立て(寸法は道路標識令 別表第二 / 交通規制基準 第 46 図例(1))。
↑ 幅の由来: {‘lanes’: 192, ‘default’: 375, ‘width’: 19}(width タグ / lanes × 車線幅 / 種別の既定)。
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_driving_japan_town/07_japan_speed.png)
↑ 停止線: [(3.3, ‘intersection’), (15.2, ‘intersection’), (133.1, ‘intersection’), (396.4, ‘intersection’), (419.3, ‘intersection’), (432.1, ‘intersection’…
↑ The animation ―― 左の車線中心を通して走る車載カメラ(JP 法規パック): 赤で停止 → 2 秒で青 → 発進、一時停止では止まって確認。道路: © OpenStreetMap contributors (ODbL) / 建物: 出典 国土交通省 Project PLATEAU
py -3.11 examples/poc_driving_japan_town.py
Source: examples/poc_driving_japan_town.py
This run produced 7 figures in total - see them all
Ops used (notes): citygml_synthetic · intersection · japan_stats · japan_world · jp_sign_stop_mesh · jp_stop_marking_mesh · latlon_to_local · osm_parse · osm_road_graph · osm_road_loops · osm_road_mask · osm_route · osm_synthetic · plateau_parse · town_checks · town_run · world_camera
| The user’s remark “setting your own traps and clearing them yourself has its limits” led to a new rule: bring at least one of the truth, the gate or the subject in from outside. Earlier driving PoCs planted their own ground truth in a synthetic world, wrote their own gates and scored their own driver. Here the scenario is a public CommonRoad (TUM) scenario (BSD-3), the judge is TUM’s drivability-checker (it does not install on Windows, so it runs in WSL and tools/check_solution_json.py writes the verdict as JSON), and only the driver is ours. The new module drivecommonroad (10 ops) reads 2020a XML itself (lanelet boundaries, dynamic obstacles, planning problem, sign 274 speed limits, stopLine), chains lanelets by successor, drives with IDM longitudinally and pure pursuit laterally under kinematic single-track (KS) dynamics (rear-axle reference, RK4, BMW_320i parameters copied from commonroad-vehicle-models’ parameters_vehicle2), stops at stop lines and writes the official solution XML (ksTrajectory). It also carries a second implementation of the judge (feasibility by re-integrating the recorded inputs to 2 cm / 0.03 rad, collision by separating axes, road boundary by point-in-polygon) to set beside the official JSON. Figures: a bird’s-eye view (lanelets, obstacle tracks, ego, goal lanelet), speed and acceleration, gaps to each obstacle, six frames (start, before the junction, turning left, closest to oncoming, entering the goal lanelet, stopped), a table of our verdicts against the official ones, and the sweep table. 22 gates: on the synthetic T-junction the reader (4 lanelets, 1 obstacle, 274 → 13.89 m/s), route 98.84 m = Σ centrelines (1e-6), KS straight line to 1e-9 and circle radius l_wb/tan δ (1e-6), goal reached with | a | ≤ 3 and v ≤ limit, stop 0.90 m short of the stop line, feasible (max_pos_err 0), no collision, a driver that ignores the car ahead collides at (step 52, obstacle 2), the blocked scene stops short, 201 ksStates = steps with positions = rear axle + b (1e-12). On the real ZAM_Tjunction-1_1_T-1 (12 lanelets, 5 obstacles, limit 14.0, route 347.6 m) the default IDM hits oncoming car 1; the 14th gap-acceptance setting (a_lat 8.0, lookahead 5.0) yields a 148-state run (v_max 9.82 m/s, 0.83 m lateral error) that the official checker accepts: valid, goal reached, feasible, no collision, n_states 148. The negative control (the gentle default driver) collides at (68, 1) in our check and the official checker also reports obstacle_collision true and valid false (feasible true), so our five verdicts agree with the official ones for both runs. Honestly: ZAM is not solvable by IDM alone and was solved by the sweep (no yielding behaviour; waiting would miss the goal time window); 8 m/s² lateral is uncomfortable though inside the checker’s friction circle (8.1 < 11.5); the public 2020a scenarios contain no traffic lights, stop lines or stop signs 206, so rule scoring is synthetic only; the official verdict is the WSL JSON (our feasibility re-integrates the recorded inputs with the same RK4, hence zero error); a longitudinal-only driver still collides in Speyer and Moabit. Source: CommonRoad scenarios (TUM, BSD-3). The data are not committed (FULLSEYE_COMMONROAD_DATA). |
| *↑ Driving Our Own Driver Through Someone Else’s Scenario and Submitting to Someone Else’s Judge — CommonRoad Public Scenarios and TUM’s Official Checker ―― The user’s remark “setting your own traps and clearing them yourself has its limits” led to a new rule: bring at least one of the truth, the gate or the subject in from outside. Earlier driving PoCs planted their own ground truth in a synthetic world, wrote their own gates and scored their own driver. Here the scenario is a public CommonRoad (TUM) scenario (BSD-3), the judge is TUM’s drivability-checker (it does not install on Windows, so it runs in WSL and tools/check_solution_json.py writes the verdict as JSON), and only the driver is ours. The new module drivecommonroad (10 ops) reads 2020a XML itself (lanelet boundaries, dynamic obstacles, planning problem, sign 274 speed limits, stopLine), chains lanelets by successor, drives with IDM longitudinally and pure pursuit laterally under kinematic single-track (KS) dynamics (rear-axle reference, RK4, BMW_320i parameters copied from commonroad-vehicle-models’ parameters_vehicle2), stops at stop lines and writes the official solution XML (ksTrajectory). It also carries a second implementation of the judge (feasibility by re-integrating the recorded inputs to 2 cm / 0.03 rad, collision by separating axes, road boundary by point-in-polygon) to set beside the official JSON. Figures: a bird’s-eye view (lanelets, obstacle tracks, ego, goal lanelet), speed and acceleration, gaps to each obstacle, six frames (start, before the junction, turning left, closest to oncoming, entering the goal lanelet, stopped), a table of our verdicts against the official ones, and the sweep table. 22 gates: on the synthetic T-junction the reader (4 lanelets, 1 obstacle, 274 → 13.89 m/s), route 98.84 m = Σ centrelines (1e-6), KS straight line to 1e-9 and circle radius l_wb/tan δ (1e-6), goal reached with | a | ≤ 3 and v ≤ limit, stop 0.90 m short of the stop line, feasible (max_pos_err 0), no collision, a driver that ignores the car ahead collides at (step 52, obstacle 2), the blocked scene stops short, 201 ksStates = steps with positions = rear axle + b (1e-12). On the real ZAM_Tjunction-1_1_T-1 (12 lanelets, 5 obstacles, limit 14.0, route 347.6 m) the default IDM hits oncoming car 1; the 14th gap-acceptance setting (a_lat 8.0, lookahead 5.0) yields a 148-state run (v_max 9.82 m/s, 0.83 m lateral error) that the official checker accepts: valid, goal reached, feasible, no collision, n_states 148. The negative control (the gentle default driver) collides at (68, 1) in our check and the official checker also reports obstacle_collision true and valid false (feasible true), so our five verdicts agree with the official ones for both runs. Honestly: ZAM is not solvable by IDM alone and was solved by the sweep (no yielding behaviour; waiting would miss the goal time window); 8 m/s² lateral is uncomfortable though inside the checker’s friction circle (8.1 < 11.5); the public 2020a scenarios contain no traffic lights, stop lines or stop signs 206, so rule scoring is synthetic only; the official verdict is the WSL JSON (our feasibility re-integrates the recorded inputs with the same RK4, hence zero error); a longitudinal-only driver still collides in Speyer and Moabit. Source: CommonRoad scenarios (TUM, BSD-3). The data are not committed (FULLSEYE_COMMONROAD_DATA).* |
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_driving_commonroad/02_commonroad_speed.png) |
| *↑ The measurement ―― IDM の希望速度 v₀ = min(制限, 曲率の許容 √(a_lat/ | κ | ))。a は区分一定(RK4 の 1 step ごと)。 | a | ≤ max(a_max 1.5, b_max 3.0)。 (figure labels are in Japanese; the numbers are the same)* |
↑ 近似(中心間距離 − 両車の半長)。
↑ 俯瞰の 6 コマ(matplotlib で描いた PNG)。
↑ 公式 = commonroad-drivability-checker 2025.4.0。
↑ IDM だけでは対向車 obs 1 より先に左折できず、曲がりの許容横加速度と見通しを順に上げた。
py -3.11 examples/poc_driving_commonroad.py
Source: examples/poc_driving_commonroad.py
This run produced 6 figures in total - see them all
Ops used (notes): boundary · cr_checker_result · cr_collision · cr_drive · cr_drive_sweep · cr_feasible · cr_read · cr_route · cr_solution_xml · cr_synthetic · ks_step
| The first exhibit of the physics-simulation × Fullseye series (the physics side of the rule ‘home-made traps have a ceiling: bring in the truth, the gate or the subject from outside’). Two things come from outside. The theorem: Whitney 1982 (ASME J. Dyn. Sys. Meas. Control 104(1), DOI 10.1115/1.3149634; the paper itself is paywalled and was not read — the formulas are taken from the author’s own MIT OCW 2.875 Class 3 slides: two-point contact depth l/d = c/θ, θ_m = √(2c), wedging if θ > c/μ, the jamming parallelogram with λ = l/(2rμ)). The contact physics: MuJoCo (contact points, normal forces, mj_geomDistance). No learning — only the rule measure → correct → descend → slow down on force. Three things are our own: the two-point depth made exact for a 3-D cylinder, l tan θ = 2R − r(cos θ + sec θ) (derived; l sin θ ≈ 2c_r to fourth order), a second implementation that predicts the number of contact points from the true pose by geometry alone, and a measurement that reads the hole centre (anti-aliased edge lifted onto the plate plane, then a circle fit) and the peg tip (known-radius cylinder on the depth points, the tip from the silhouette end matched against the projected rim circle) in 3-D from one wrist RGB-D frame. New module pegsim, 18 ops (plus 9 facade functions that need mujoco). Figures: wrist-camera overlay (green = truth, red = estimate, yellow = edge points, 3× inset), insertion GIF with correction, GIF of the peg sliding down the chamfer without correction, three theoretical l₂ curves with the MuJoCo measurements, the jamming diagram at l = 2 / 8 mm, and the success table. 23 gates: Whitney’s quantities by hand (c = 0.0385, θ_m = 15.9°, chamfer tolerance 1.2 mm, c/μ = 7.35°), l₂ sin θ = 0.3977–0.3999 mm (2c_r = 0.400), the 2-D rectangle approximation is 0.52 mm off at 6° (counter-example), the second implementation switches from one to two points at l₂ ∓ 0.05 mm, PnP identity to 1e-9, the known-radius circle fit agrees with measure.fit_circle to 1e-9 on a full circle and has a median centre error of 0.15 px on a 25° arc (free fit 3.3 px), the synthetic RGB-D (analytic ray cast, no mujoco) gives the hole centre to 0.004 px and the offset to 0.0004 mm, and the image-plane ellipse centre is 0.80 px away from the projected circle centre (why we fit in 3-D). MuJoCo gates: the depth buffer under MSAA holds sample 0 at (−0.125, +0.375) px (pixel centre with offsamples=0); over 8 poses ( | ε | ≤ 3 mm, | θ | ≤ 3°) the hole centre is within 0.18 px (free circle) / 0.045 px (drawing radius R + W), the offset within 0.024 / 0.010 mm, the tip’s lateral error 0.003 px; l₂ measured by bisection on mj_geomDistance matches the closed form to 0.017–0.070 mm (1.5–6°) with l₂ sin θ = 0.396–0.398 mm; the contact state switches one-point / two-point at l₂ ∓ 0.3 mm; the insertion with ε = (2, 1) mm and θ = 2° succeeds and the first two-point tick has l sin θ = 0.400 mm; 7 servo iterations take the true offset from 2.24 to 0.03 mm; without correction ε₀ = 1 mm is pushed in by the chamfer; the grid ε₀ {0, 1, 2, 3} mm × θ₀ {0, 1.5, 3}° succeeds up to 1 mm without correction (2 mm and more stop at the mouth — the chamfer tolerance is 1.2 mm) and 12 / 12 with it. Honestly: the tip’s axial position comes from the coverage of one silhouette pixel and is off by up to 0.50 px (the 0.3 px target is not met); the raw agreement between predicted and simulated contact counts is 0.69 (0.96 within ±1 — soft contacts flicker between 0/1 and 1/2 points); the wrist is a rigid body on springs with no actuator lag, flex or calibration error; wedging cannot occur at θ ≤ 3° with μ = 0.3 and was not provoked; the original paper was not read. Traps: MuJoCo’s 4× MSAA depth is the depth at sample 0, not at the pixel centre (measured on a tilted plane; depth is rendered from a second compile with offsamples=0), the default 28 slices of a cylinder pull the radius 0.04 mm inwards (numslices=128), and the first 2-D rectangle approximation D = d/cos θ + l tan θ is 0.5 mm wrong at θ = 6°. Without mujoco the MuJoCo gates are skipped and the 12 numpy gates decide. 37 s. |
| *↑ Peg-in-Hole with a Compliant Wrist — Whitney’s Quasi-Static Geometry as the Gate, a Wrist Camera to Centre the Peg, MuJoCo Contacts to Check It ―― The first exhibit of the physics-simulation × Fullseye series (the physics side of the rule ‘home-made traps have a ceiling: bring in the truth, the gate or the subject from outside’). Two things come from outside. The theorem: Whitney 1982 (ASME J. Dyn. Sys. Meas. Control 104(1), DOI 10.1115/1.3149634; the paper itself is paywalled and was not read — the formulas are taken from the author’s own MIT OCW 2.875 Class 3 slides: two-point contact depth l/d = c/θ, θ_m = √(2c), wedging if θ > c/μ, the jamming parallelogram with λ = l/(2rμ)). The contact physics: MuJoCo (contact points, normal forces, mj_geomDistance). No learning — only the rule measure → correct → descend → slow down on force. Three things are our own: the two-point depth made exact for a 3-D cylinder, l tan θ = 2R − r(cos θ + sec θ) (derived; l sin θ ≈ 2c_r to fourth order), a second implementation that predicts the number of contact points from the true pose by geometry alone, and a measurement that reads the hole centre (anti-aliased edge lifted onto the plate plane, then a circle fit) and the peg tip (known-radius cylinder on the depth points, the tip from the silhouette end matched against the projected rim circle) in 3-D from one wrist RGB-D frame. New module pegsim, 18 ops (plus 9 facade functions that need mujoco). Figures: wrist-camera overlay (green = truth, red = estimate, yellow = edge points, 3× inset), insertion GIF with correction, GIF of the peg sliding down the chamfer without correction, three theoretical l₂ curves with the MuJoCo measurements, the jamming diagram at l = 2 / 8 mm, and the success table. 23 gates: Whitney’s quantities by hand (c = 0.0385, θ_m = 15.9°, chamfer tolerance 1.2 mm, c/μ = 7.35°), l₂ sin θ = 0.3977–0.3999 mm (2c_r = 0.400), the 2-D rectangle approximation is 0.52 mm off at 6° (counter-example), the second implementation switches from one to two points at l₂ ∓ 0.05 mm, PnP identity to 1e-9, the known-radius circle fit agrees with measure.fit_circle to 1e-9 on a full circle and has a median centre error of 0.15 px on a 25° arc (free fit 3.3 px), the synthetic RGB-D (analytic ray cast, no mujoco) gives the hole centre to 0.004 px and the offset to 0.0004 mm, and the image-plane ellipse centre is 0.80 px away from the projected circle centre (why we fit in 3-D). MuJoCo gates: the depth buffer under MSAA holds sample 0 at (−0.125, +0.375) px (pixel centre with offsamples=0); over 8 poses ( | ε | ≤ 3 mm, | θ | ≤ 3°) the hole centre is within 0.18 px (free circle) / 0.045 px (drawing radius R + W), the offset within 0.024 / 0.010 mm, the tip’s lateral error 0.003 px; l₂ measured by bisection on mj_geomDistance matches the closed form to 0.017–0.070 mm (1.5–6°) with l₂ sin θ = 0.396–0.398 mm; the contact state switches one-point / two-point at l₂ ∓ 0.3 mm; the insertion with ε = (2, 1) mm and θ = 2° succeeds and the first two-point tick has l sin θ = 0.400 mm; 7 servo iterations take the true offset from 2.24 to 0.03 mm; without correction ε₀ = 1 mm is pushed in by the chamfer; the grid ε₀ {0, 1, 2, 3} mm × θ₀ {0, 1.5, 3}° succeeds up to 1 mm without correction (2 mm and more stop at the mouth — the chamfer tolerance is 1.2 mm) and 12 / 12 with it. Honestly: the tip’s axial position comes from the coverage of one silhouette pixel and is off by up to 0.50 px (the 0.3 px target is not met); the raw agreement between predicted and simulated contact counts is 0.69 (0.96 within ±1 — soft contacts flicker between 0/1 and 1/2 points); the wrist is a rigid body on springs with no actuator lag, flex or calibration error; wedging cannot occur at θ ≤ 3° with μ = 0.3 and was not provoked; the original paper was not read. Traps: MuJoCo’s 4× MSAA depth is the depth at sample 0, not at the pixel centre (measured on a tilted plane; depth is rendered from a second compile with offsamples=0), the default 28 slices of a cylinder pull the radius 0.04 mm inwards (numslices=128), and the first 2-D rectangle approximation D = d/cos θ + l tan θ is 0.5 mm wrong at θ = 6°. Without mujoco the MuJoCo gates are skipped and the 12 numpy gates decide. 37 s.* |
↑ The measurement ―― 傾き θ で二点接触が始まる深さ l₂(最狭部から)。3-D の円柱で厳密にした (1′) と小角の式 l₂ sin θ = 2c_r は重なり、2-D の長方形近似は θ = 6° で 0.5 mm 浅い。点は MuJoCo の mj_geomDistance の二分法で測った値(閉形式と 0.07 mm 以内)。1° では 22.9 mm と穴の深さ 20 mm を超え、二点接触は起きない。 (figure labels are in Japanese; the numbers are the same)
↑ Whitney のかじりの図(OCW p.34): 二点接触中にペグが進むのは加える力の比がこの平行四辺形の内側にあるとき。
↑ 初期横ずれ ε₀ × 傾き θ₀ の成功 / 失敗(各 1 走行、ずれの向きは 30°)。
↑ The animation ―― 補正ありの挿入(ε₀ = (2, 1) mm、θ₀ = 2°、側面カメラ、0.15 s ごと): サーボで穴の上に寄せ、下げ、一点 → 二点接触を経て 15 mm。
↑ The animation ―― 補正なし(ε₀ = 1 mm < 面取りの許容 1.2 mm、θ₀ = 2°): 面取りが柔らかい手首を横へ押し、ペグが滑り込む。2 mm では入口で止まる。
py -3.11 examples/poc_pegsim_insertion.py
Source: examples/poc_pegsim_insertion.py
This run produced 6 figures in total - see them all
Ops used (notes): camera_world_to_cv · chamfer_capture · circle_fit_known_radius · contact_state_predict · jamming_diagram · peg_measure_overlay · peg_offset_from_rgbd · peg_params · peg_scene_mjcf · peg_synthetic_rgbd · project_points · two_point_depth · wedging_check · whitney_clearance
| The third exhibit of the physics-simulation × Fullseye series (the first was peg-in-hole, the second the three visuotactile exhibits). The subject is a low-cost air-hockey robot (Shinjo, Beltran-Hernandez, Hamaya, Tanaka, IROS 2024, doi 10.1109/iros58592.2024.10801458): a five-bar linkage driven by position-control servos, with the chain camera → puck detection → velocity estimation → trajectory prediction → motion planning. The chain is rebuilt with existing Fullseye parts (balltrack.ball_detect / ball_track / kalman_ca, ballistics.slide_stop_distance, calib.image_to_world_plane) and the new module puck (26 ops), learning-free and rule-based throughout, with ground truth brought in from outside on three fronts. (1) Closed forms: sliding on the air cushion is Coulomb’s uniform deceleration a = μg (stopping distance v₀²/(2μg)); a wall flips the normal component by −e and keeps kₜ of the tangential one (two coefficients of restitution; the notation follows Cross 2022, Eur. J. Phys., doi 10.1088/1361-6404/ac4b47, oblique impact of a disc; the Spong 2001 impact model could not be read and is unverified); between walls the path is stitched from per-segment closed forms (a quadratic or a logarithm) — against the mirror method as a second implementation (no friction, e = kₜ = 1) it agrees to 3e-15 m over 200 launches with up to 17 reflections, and to 1.5e-5 m against semi-implicit Euler at dt = 1e-5. (2) Five-bar kinematics: the closed form of two two-link arms meeting at the striker (circle–circle intersections); FK∘IK returns to 2e-15 m on 400 workspace points and the reach circle l₁ + l₂ separates inside from outside at ±1e-6 m. (3) An external simulator: the table MJCF of the Robot Air Hockey Challenge (Liu et al., arXiv 2411.05718, MIT; the file is not bundled, it lives under the environment variable FULLSEYE_AIRHOCKEY_DATA and is used only with –full). ★The second implementation exposed a model difference: this table excludes the puck–surface contact and slows the puck through joint viscous damping c = 0.005 N·s/m (m = 0.01 kg → c/m = 0.5 /s, a 2 s time constant). It is not Coulomb deceleration (measured log-speed slope −0.4999 /s). The rims are soft contacts, so e and kₜ are not parameters but measured outcomes (e_n = 0.739, kₜ = 0.852). Adding model=”viscous” to the predictor matches MuJoCo to 0.110 mm before the first wall and, with the measured e and kₜ, to 0.4 mm 0.4 s after one bounce. Our own Coulomb MJCF (a cylinder on a frictional plane) decelerates 3.3 % away from μg (whether MuJoCo’s soft-contact friction or our MJCF settings is not resolved). The synthetic top-down camera draws the puck with coverage anti-aliasing and adds motion during the exposure as the mean of sub-exposures: the blurred centroid advances by v·τ/2 (closed form, matched to 0.004 px — it vanishes if the frame is stamped at mid-exposure). Detection is a facade over ball_detect with the threshold just below the table value, 0.85 — the weight is (threshold − pixel), so at 0.5 the edge pixels are cut and a phase-dependent bias of 0.08 px appears (measured; 0.005 px at 0.85). Velocity is a least-squares fit of the uniform-deceleration model with the direction û fixed (û iterated three times; the viscous case is linear in the basis (1 − e^{−kτ})/k) and σ_v comes from σ²(AᵀA)⁻¹. The striker plan is a rule: IK to the crossing point, each servo at a constant rate ≤ 6 rad/s, too_late if it cannot arrive first, unreachable outside the workspace, no_crossing if the path never crosses (fail-closed). Figures: a 343 px/m frame with the 16 detections, the predicted path and the crossing point (0.0 mm from the truth, 756 ms ahead) next to a 1:1 crop of the puck (nearest neighbour ×8) with the sub-pixel centroid (0.004 px from the truth); a GIF where the 95 % band of the crossing narrows as frames accumulate on a coarse camera (100 px/m, noise σ 0.06); velocity error vs N inside 3σ; a GIF of the five-bar linkage arriving first (move 256 ms, crossing 883 ms); where it breaks (blur, an unknown wall kₜ fans the prediction after a bounce, 45 px per frame breaks ball_track’s association); MuJoCo vs the viscous closed form vs Coulomb (three curves with –full). 16 gates (numpy only, 1.6 s, 7 s with figures) plus 6 with –full (MuJoCo, 13 s): stopping distance = ballistics (0), wall energy ratio e², mirror method 3e-15 m, self-consistency + Euler 1.5e-5 m, FK∘IK 2e-15 m, the reach boundary and the reachable interval | y | ≤ 0.369 on the defence line x = −0.75 (the puck can be at | y | ≤ 0.487, so the corners are out of reach — stated honestly), detection centroids over 50 positions max 0.0059 px / rms 0.0024 px (0.012 mm), velocity from exact positions 7e-16 and from detections at N = 12 0.01 % (σ_v 0.2 mm/s), crossing points from noisy detections over 20 launches rms 0.2 mm / max 0.5 mm (threshold 3σ_mc + a 2 mm floor because the 60-sample Monte-Carlo σ itself wobbles by ±10 %), the plan E = (−0.750, 0.350) with a 0.256 s move, 8 m/s → too_late, y = 0.47 → unreachable, blur 0.004 px, six spelling-breaks plus None without a crossing, μ to 0.7 % (the 0.3 s bend is 0.8 px), e 0.7999 and kₜ 0.9001, fast puck 10/10 at 20 and 35 px per frame but 1/10 at 45 and 80 (max_jump 40 px, 27 m/s at 120 fps), kalman_ca innovation 2.7e-13 m; –full: viscous slope −0.4999 /s, closed form 0.110 mm, 0.4 mm after the bounce, Coulomb MJCF 3.3 %, render → colour detection → pinhole bias (−2.6, 2.3) mm and scatter 0.35 mm (0.12 px; the bias comes from the site height and render quantisation and is subtracted in the chain), the full chain over 20 launches: 14 crossed the defence line, all 12 within reach were intercepted, 2 in the corners were declined, crossing error median 1.2 mm / max 14.9 mm (inside the 71.65 mm puck + mallet radius), timing max 21 ms. Honestly: the linkage dimensions (d 0.30, l₁ 0.25, l₂ 0.35 m), the 6 rad/s servo rate, the 40 mm mallet, the defence line x = −0.75 and the closed-form defaults μ 0.02 / e 0.8 / kₜ 0.9 are all assumptions (the paper’s hardware numbers could not be read); no real footage is used (synthetic + MuJoCo, no lighting, lens distortion or rolling shutter; the synthetic detection error of 0.006 px is more than an order of magnitude better than reality); the slip/grip regimes and spin of a wall impact are not implemented; e and kₜ are estimated by splitting the track at the frame where the heading changes most (a single bounce only). |
| *↑ Tracking, Predicting and Returning an Air-Hockey Puck with a Five-Bar Linkage — No Learning, Closed Forms and an External Simulator as the Gates ―― The third exhibit of the physics-simulation × Fullseye series (the first was peg-in-hole, the second the three visuotactile exhibits). The subject is a low-cost air-hockey robot (Shinjo, Beltran-Hernandez, Hamaya, Tanaka, IROS 2024, doi 10.1109/iros58592.2024.10801458): a five-bar linkage driven by position-control servos, with the chain camera → puck detection → velocity estimation → trajectory prediction → motion planning. The chain is rebuilt with existing Fullseye parts (balltrack.ball_detect / ball_track / kalman_ca, ballistics.slide_stop_distance, calib.image_to_world_plane) and the new module puck (26 ops), learning-free and rule-based throughout, with ground truth brought in from outside on three fronts. (1) Closed forms: sliding on the air cushion is Coulomb’s uniform deceleration a = μg (stopping distance v₀²/(2μg)); a wall flips the normal component by −e and keeps kₜ of the tangential one (two coefficients of restitution; the notation follows Cross 2022, Eur. J. Phys., doi 10.1088/1361-6404/ac4b47, oblique impact of a disc; the Spong 2001 impact model could not be read and is unverified); between walls the path is stitched from per-segment closed forms (a quadratic or a logarithm) — against the mirror method as a second implementation (no friction, e = kₜ = 1) it agrees to 3e-15 m over 200 launches with up to 17 reflections, and to 1.5e-5 m against semi-implicit Euler at dt = 1e-5. (2) Five-bar kinematics: the closed form of two two-link arms meeting at the striker (circle–circle intersections); FK∘IK returns to 2e-15 m on 400 workspace points and the reach circle l₁ + l₂ separates inside from outside at ±1e-6 m. (3) An external simulator: the table MJCF of the Robot Air Hockey Challenge (Liu et al., arXiv 2411.05718, MIT; the file is not bundled, it lives under the environment variable FULLSEYE_AIRHOCKEY_DATA and is used only with –full). ★The second implementation exposed a model difference: this table excludes the puck–surface contact and slows the puck through joint viscous damping c = 0.005 N·s/m (m = 0.01 kg → c/m = 0.5 /s, a 2 s time constant). It is not Coulomb deceleration (measured log-speed slope −0.4999 /s). The rims are soft contacts, so e and kₜ are not parameters but measured outcomes (e_n = 0.739, kₜ = 0.852). Adding model=”viscous” to the predictor matches MuJoCo to 0.110 mm before the first wall and, with the measured e and kₜ, to 0.4 mm 0.4 s after one bounce. Our own Coulomb MJCF (a cylinder on a frictional plane) decelerates 3.3 % away from μg (whether MuJoCo’s soft-contact friction or our MJCF settings is not resolved). The synthetic top-down camera draws the puck with coverage anti-aliasing and adds motion during the exposure as the mean of sub-exposures: the blurred centroid advances by v·τ/2 (closed form, matched to 0.004 px — it vanishes if the frame is stamped at mid-exposure). Detection is a facade over ball_detect with the threshold just below the table value, 0.85 — the weight is (threshold − pixel), so at 0.5 the edge pixels are cut and a phase-dependent bias of 0.08 px appears (measured; 0.005 px at 0.85). Velocity is a least-squares fit of the uniform-deceleration model with the direction û fixed (û iterated three times; the viscous case is linear in the basis (1 − e^{−kτ})/k) and σ_v comes from σ²(AᵀA)⁻¹. The striker plan is a rule: IK to the crossing point, each servo at a constant rate ≤ 6 rad/s, too_late if it cannot arrive first, unreachable outside the workspace, no_crossing if the path never crosses (fail-closed). Figures: a 343 px/m frame with the 16 detections, the predicted path and the crossing point (0.0 mm from the truth, 756 ms ahead) next to a 1:1 crop of the puck (nearest neighbour ×8) with the sub-pixel centroid (0.004 px from the truth); a GIF where the 95 % band of the crossing narrows as frames accumulate on a coarse camera (100 px/m, noise σ 0.06); velocity error vs N inside 3σ; a GIF of the five-bar linkage arriving first (move 256 ms, crossing 883 ms); where it breaks (blur, an unknown wall kₜ fans the prediction after a bounce, 45 px per frame breaks ball_track’s association); MuJoCo vs the viscous closed form vs Coulomb (three curves with –full). 16 gates (numpy only, 1.6 s, 7 s with figures) plus 6 with –full (MuJoCo, 13 s): stopping distance = ballistics (0), wall energy ratio e², mirror method 3e-15 m, self-consistency + Euler 1.5e-5 m, FK∘IK 2e-15 m, the reach boundary and the reachable interval | y | ≤ 0.369 on the defence line x = −0.75 (the puck can be at | y | ≤ 0.487, so the corners are out of reach — stated honestly), detection centroids over 50 positions max 0.0059 px / rms 0.0024 px (0.012 mm), velocity from exact positions 7e-16 and from detections at N = 12 0.01 % (σ_v 0.2 mm/s), crossing points from noisy detections over 20 launches rms 0.2 mm / max 0.5 mm (threshold 3σ_mc + a 2 mm floor because the 60-sample Monte-Carlo σ itself wobbles by ±10 %), the plan E = (−0.750, 0.350) with a 0.256 s move, 8 m/s → too_late, y = 0.47 → unreachable, blur 0.004 px, six spelling-breaks plus None without a crossing, μ to 0.7 % (the 0.3 s bend is 0.8 px), e 0.7999 and kₜ 0.9001, fast puck 10/10 at 20 and 35 px per frame but 1/10 at 45 and 80 (max_jump 40 px, 27 m/s at 120 fps), kalman_ca innovation 2.7e-13 m; –full: viscous slope −0.4999 /s, closed form 0.110 mm, 0.4 mm after the bounce, Coulomb MJCF 3.3 %, render → colour detection → pinhole bias (−2.6, 2.3) mm and scatter 0.35 mm (0.12 px; the bias comes from the site height and render quantisation and is subtracted in the chain), the full chain over 20 launches: 14 crossed the defence line, all 12 within reach were intercepted, 2 in the corners were declined, crossing error median 1.2 mm / max 14.9 mm (inside the 71.65 mm puck + mallet radius), timing max 21 ms. Honestly: the linkage dimensions (d 0.30, l₁ 0.25, l₂ 0.35 m), the 6 rad/s servo rate, the 40 mm mallet, the defence line x = −0.75 and the closed-form defaults μ 0.02 / e 0.8 / kₜ 0.9 are all assumptions (the paper’s hardware numbers could not be read); no real footage is used (synthetic + MuJoCo, no lighting, lens distortion or rolling shutter; the synthetic detection error of 0.006 px is more than an order of magnitude better than reality); the slip/grip regimes and spin of a wall impact are not implemented; e and kₜ are estimated by splitting the track at the frame where the heading changes most (a single bounce only).* |
↑ The measurement ―― 粗いカメラ(100 px/m、雑音 σ 0.06)でも、コマが増えるほど守備線上の交点の 95 % 帯(赤)が細る: N = 3 で 145 mm → N = 16 で 7 mm。緑の十字 = 真の交点。 (figure labels are in Japanese; the numbers are the same)
↑ 検出から最小二乗で出した速度の誤差は N とともに縮み、最小二乗の分散の 3σ(破線)の内側に収まる(N = 12 で 0.01 %、N = 24 で 0.01 %)。
↑ どこで壊れるか。
↑ Challenge の台では速さが指数で落ちる(粘性減衰 c/m = 0.5 /s、実測の傾き -0.4999 /s)。
↑ The animation ―― 8 コマで予測した交点へ、5 節リンク(一定角速度 ≤ 6 rad/s、寸法は仮定)が 191 ms で先回りして打具(緑の円、半径 40 mm は仮定)を置く。交点の到着は 823 ms 先。
py -3.11 examples/poc_air_hockey_intercept.py
Source: examples/poc_air_hockey_intercept.py
This run produced 6 figures in total - see them all
Ops used (notes): crosshair · fivebar_fk · fivebar_ik · fivebar_link · fivebar_reach_interval · fivebar_trajectory · fivebar_workspace · kalman_ca · puck_camera · puck_crossing_point · puck_detect · puck_mirror_path · puck_mu_from_decel · puck_pinhole_camera · puck_render_frame · puck_restitution_from_wall · puck_scene_mjcf · puck_slide_predict · puck_state_at · puck_stop_distance · puck_synth_frames · puck_table · puck_track · puck_velocity_estimate … (+5)
| The capstone of the peg-in-hole thread of the physics-simulation × Fullseye series (pegsim, soft-wrist insertion, 2026.195). The prior work (Shirasaka, Beltran-Hernandez, Hamaya, Ushiku, arXiv:2509.17666, ICRA 2026, code unpublished) structures soft-wrist insertion as contact formation — a sequence of contact states that constrain the degrees of freedom one by one — and lets a VLM judge the failure mode from the final pose and an image before picking a recovery skill. Here the VLM is replaced by a rule table: the observation (contact kind none / plate / chamfer / one_point / two_point / floor, depth band above / mouth / hole / bottom, stall, whether the tilt at the onset of two-point contact is above c/μ, whether the force ratio lies inside the jamming parallelogram, whether the tip offset from the hole centre is within W + c_r) is discretised into a six-field signature and looked up in a 12-row table. At most one row matches (a validator first checks pairwise exclusivity and that all 8 classes are reachable); if none does the answer is unknown (the table decides 362 of the 576 signatures in the vocabulary and never guesses the rest: fail-closed). Ground truth comes from outside on two fronts. (1) The theorem: Whitney 1982 (the original is paywalled and unread; the equations are from the author’s own MIT OCW 2.875 Class 3 slides): wedging θ > c/μ (p.28), the jamming parallelogram λ = l/(2rμ) (p.34). The four vertices are re-derived from the planar statics of two-point contact (both points sliding down with Coulomb friction: F_x = f₂ − f₁, F_z = μ(f₁ + f₂), M = μrf₁ − (μr + l)f₂) and agree with pegsim’s vertices (the OCW values) to 2.2e-16 — this derivation fixes the sign convention of the force ratios (e_x from the wall the tip touches towards the opposite mouth edge, F_z positive when pushing, M = M⃗·(e_x × ẑ)). (2) The physics engine: MuJoCo’s contact points, normal forces and the wrist force sensor (–full). The tip load (F⃗, M⃗_tip) is read from the deflection of the wrist springs (a rule that needs no external F/T sensor): over the 3,458 quasi-static ticks spent stalled, the estimated load plus gravity balances the sum of the contact forces to 0.35 N, and agrees with the wrist force sensor (site frame → world) to 0.026 N. Failures are injected on purpose: an offset of 2.5 mm the chamfer cannot catch (> W + c_r = 1.2 mm), wedging with μ 0.8 and θ₀ 4.5° (> c/μ = 2.76°), jamming with θ₀ 3° and a +3 mm lateral target shift towards the tilt once the tip is 4 mm in, a plug at 6 mm depth, a decoy hole 26 mm away. The chain is descend (slow at 2.5 N, 12 N cap) → stall (advance < 0.05 mm over a 30-tick window, armed after 0.5 mm of progress) → signature → table → recovery primitive (lift 6 mm and re-centre with the wrist RGB-D / lift 10 mm and return to the fixture target / retract 4 mm and undo the measured tilt / pick the 0.5 mm carriage × 0.5° wrist step that maximises the parallelogram margin in a linear model / lift and abort). Figures: the flip probability of the offset field when σ = 0.05 mm noise is added at the W + c_r = 1.2 mm boundary (0.508 at the boundary, 0.168 / 0.157 at ±1σ, within 0.03 of Φ(−1); dashed = theory), the confusion matrix of the 6 injected classes (6 / 6 from truth signatures, 6 / 6 from the vision signatures), the jamming diagram with the trajectory of force ratios estimated from the wrist load (the tick where the jam stall is detected lies outside its own-depth parallelogram at l = 5.3 mm with margin −0.50, the 321 ticks of nominal two-point sliding lie inside with min 0.18 / median 0.20 — the diagram widens with depth, so two parallelograms are drawn), depth vs tick for the wedge (stall → wedging in the same tick → retract + tilt reset → re-descend and succeed), a 48-frame wrist-camera GIF (‘detected: wedging -> retract_reduce_tilt’ appears, then the re-descent), and the release-probe table. ★The release probe shows Whitney’s distinction physically: the wedge (μ 0.8, θ 4.7°) stays at 5.90 mm when the push is removed (F_z 2.03 → 0.64 N) and even when pulled by 2 mm (F_z −0.39 N) — compression is stored inside; the jam (μ 0.3, θ 4.4°, F_x 1.3 N) advances under its own weight from 6.25 to 7.21 mm once the lateral force is also removed and comes back to 5.30 mm when pulled — a matter of force direction. 12 gates (numpy only, 0.6 s) plus 8 with –full (MuJoCo, 14 s, 35 s with figures): table completeness and exclusivity, parallelogram vertices 2.2e-16, the wedging boundary at c/μ ∓ 1e-9 rad (7.353° at μ 0.3, 2.755° at 0.8), stall detection (no false alarm on three monotone descents plus a hover, onset 27 ticks after the true stop, armed at tick 17), all 362 row signatures classify to their row, three unknown signatures and four spelling breaks, the closed-form wrist load to 1e-12, flip probability = Φ(− | z | ), synthetic RGB-D (analytic ray cast, no mujoco) offsets at four poses to 0.002 mm → field → class = truth, discretisation boundaries, the summary latency of 30 ticks and the confusion matrix, the MJCF (two force/torque sensors, the plug, 109 decoy geoms plus a 4-box frame, ValueError for a plug outside the hole or an overlapping decoy); –full: force closure, the injection grid 6 classes × {no recovery, recovery} with 6 / 6 from truth and from vision signatures and 0/5 → 5/5 (the blocked hole is correctly aborted = success), detection latencies of 32–33 ticks (= 30-tick window + ≤ 10 confirmation), 9 for the plug and 18 for the decoy, the release probe, the jamming-diagram margin (the “inside” threshold 0.15 sits between the stalled −0.50 and the sliding 0.18 — MuJoCo’s sliding boundary is ≈ 0.15 inside Whitney’s line: a 36-gon wall and soft contact), wedging only above the boundary (μ 0.8 at θ₀ 2.0° inserts, μ 0.3 at θ₀ 4.5° jams instead), the vision anchor (0.016 mm with one hole, perturbed to 0.47 mm by the decoy — stated honestly), and the recorded run. Honestly: no VLM baseline is built (nothing is lined up against something that does not exist). The 5/5 is one condition per class and is not comparable with the paper’s real-robot success rate. The “vision signature” is the hover-time wrist RGB-D anchor plus encoder integration (the tip is invisible once inside the hole). The jam test is valid only while stalled with F_z ≥ 1 N (while moving the ratios are ill-conditioned and the margin reaches −1.6 although the peg advances). A +1.6 mm shift stalls at the force cap with the ratio inside — unknown, and the table stays silent. The recovery primitives are scripted (lifts of 4 / 6 / 10 mm, ≤ 6 iterations, ≤ 3 recoveries per run). Only one jam direction (the opposite side slides in). The compliant-support condition for avoiding wedging (S = L_g/(L_g² + K_θ/K_x)) is not used because the inequality cannot be read from the slides. No tactile sensing (tacslip). |
| *↑ Detecting and Recovering Peg-Insertion Failures with a Rule Table — Whitney’s Contact States Instead of a VLM, MuJoCo as the Truth ―― The capstone of the peg-in-hole thread of the physics-simulation × Fullseye series (pegsim, soft-wrist insertion, 2026.195). The prior work (Shirasaka, Beltran-Hernandez, Hamaya, Ushiku, arXiv:2509.17666, ICRA 2026, code unpublished) structures soft-wrist insertion as contact formation — a sequence of contact states that constrain the degrees of freedom one by one — and lets a VLM judge the failure mode from the final pose and an image before picking a recovery skill. Here the VLM is replaced by a rule table: the observation (contact kind none / plate / chamfer / one_point / two_point / floor, depth band above / mouth / hole / bottom, stall, whether the tilt at the onset of two-point contact is above c/μ, whether the force ratio lies inside the jamming parallelogram, whether the tip offset from the hole centre is within W + c_r) is discretised into a six-field signature and looked up in a 12-row table. At most one row matches (a validator first checks pairwise exclusivity and that all 8 classes are reachable); if none does the answer is unknown (the table decides 362 of the 576 signatures in the vocabulary and never guesses the rest: fail-closed). Ground truth comes from outside on two fronts. (1) The theorem: Whitney 1982 (the original is paywalled and unread; the equations are from the author’s own MIT OCW 2.875 Class 3 slides): wedging θ > c/μ (p.28), the jamming parallelogram λ = l/(2rμ) (p.34). The four vertices are re-derived from the planar statics of two-point contact (both points sliding down with Coulomb friction: F_x = f₂ − f₁, F_z = μ(f₁ + f₂), M = μrf₁ − (μr + l)f₂) and agree with pegsim’s vertices (the OCW values) to 2.2e-16 — this derivation fixes the sign convention of the force ratios (e_x from the wall the tip touches towards the opposite mouth edge, F_z positive when pushing, M = M⃗·(e_x × ẑ)). (2) The physics engine: MuJoCo’s contact points, normal forces and the wrist force sensor (–full). The tip load (F⃗, M⃗_tip) is read from the deflection of the wrist springs (a rule that needs no external F/T sensor): over the 3,458 quasi-static ticks spent stalled, the estimated load plus gravity balances the sum of the contact forces to 0.35 N, and agrees with the wrist force sensor (site frame → world) to 0.026 N. Failures are injected on purpose: an offset of 2.5 mm the chamfer cannot catch (> W + c_r = 1.2 mm), wedging with μ 0.8 and θ₀ 4.5° (> c/μ = 2.76°), jamming with θ₀ 3° and a +3 mm lateral target shift towards the tilt once the tip is 4 mm in, a plug at 6 mm depth, a decoy hole 26 mm away. The chain is descend (slow at 2.5 N, 12 N cap) → stall (advance < 0.05 mm over a 30-tick window, armed after 0.5 mm of progress) → signature → table → recovery primitive (lift 6 mm and re-centre with the wrist RGB-D / lift 10 mm and return to the fixture target / retract 4 mm and undo the measured tilt / pick the 0.5 mm carriage × 0.5° wrist step that maximises the parallelogram margin in a linear model / lift and abort). Figures: the flip probability of the offset field when σ = 0.05 mm noise is added at the W + c_r = 1.2 mm boundary (0.508 at the boundary, 0.168 / 0.157 at ±1σ, within 0.03 of Φ(−1); dashed = theory), the confusion matrix of the 6 injected classes (6 / 6 from truth signatures, 6 / 6 from the vision signatures), the jamming diagram with the trajectory of force ratios estimated from the wrist load (the tick where the jam stall is detected lies outside its own-depth parallelogram at l = 5.3 mm with margin −0.50, the 321 ticks of nominal two-point sliding lie inside with min 0.18 / median 0.20 — the diagram widens with depth, so two parallelograms are drawn), depth vs tick for the wedge (stall → wedging in the same tick → retract + tilt reset → re-descend and succeed), a 48-frame wrist-camera GIF (‘detected: wedging -> retract_reduce_tilt’ appears, then the re-descent), and the release-probe table. ★The release probe shows Whitney’s distinction physically: the wedge (μ 0.8, θ 4.7°) stays at 5.90 mm when the push is removed (F_z 2.03 → 0.64 N) and even when pulled by 2 mm (F_z −0.39 N) — compression is stored inside; the jam (μ 0.3, θ 4.4°, F_x 1.3 N) advances under its own weight from 6.25 to 7.21 mm once the lateral force is also removed and comes back to 5.30 mm when pulled — a matter of force direction. 12 gates (numpy only, 0.6 s) plus 8 with –full (MuJoCo, 14 s, 35 s with figures): table completeness and exclusivity, parallelogram vertices 2.2e-16, the wedging boundary at c/μ ∓ 1e-9 rad (7.353° at μ 0.3, 2.755° at 0.8), stall detection (no false alarm on three monotone descents plus a hover, onset 27 ticks after the true stop, armed at tick 17), all 362 row signatures classify to their row, three unknown signatures and four spelling breaks, the closed-form wrist load to 1e-12, flip probability = Φ(− | z | ), synthetic RGB-D (analytic ray cast, no mujoco) offsets at four poses to 0.002 mm → field → class = truth, discretisation boundaries, the summary latency of 30 ticks and the confusion matrix, the MJCF (two force/torque sensors, the plug, 109 decoy geoms plus a 4-box frame, ValueError for a plug outside the hole or an overlapping decoy); –full: force closure, the injection grid 6 classes × {no recovery, recovery} with 6 / 6 from truth and from vision signatures and 0/5 → 5/5 (the blocked hole is correctly aborted = success), detection latencies of 32–33 ticks (= 30-tick window + ≤ 10 confirmation), 9 for the plug and 18 for the decoy, the release probe, the jamming-diagram margin (the “inside” threshold 0.15 sits between the stalled −0.50 and the sliding 0.18 — MuJoCo’s sliding boundary is ≈ 0.15 inside Whitney’s line: a 36-gon wall and soft contact), wedging only above the boundary (μ 0.8 at θ₀ 2.0° inserts, μ 0.3 at θ₀ 4.5° jams instead), the vision anchor (0.016 mm with one hole, perturbed to 0.47 mm by the decoy — stated honestly), and the recorded run. Honestly: no VLM baseline is built (nothing is lined up against something that does not exist). The 5/5 is one condition per class and is not comparable with the paper’s real-robot success rate. The “vision signature” is the hover-time wrist RGB-D anchor plus encoder integration (the tip is invisible once inside the hole). The jam test is valid only while stalled with F_z ≥ 1 N (while moving the ratios are ill-conditioned and the margin reaches −1.6 although the peg advances). A +1.6 mm shift stalls at the force cap with the ratio inside — unknown, and the table stays silent. The recovery primitives are scripted (lifts of 4 / 6 / 10 mm, ≤ 6 iterations, ≤ 3 recoveries per run). Only one jam direction (the opposite side slides in). The compliant-support condition for avoiding wedging (S = L_g/(L_g² + K_θ/K_x)) is not used because the inequality cannot be read from the slides. No tactile sensing (tacslip).* |
↑ The measurement ―― 注入した失敗 6 クラスと表の分類(真値署名): 対角 6 / 6。右列は視覚のずれ(構え時の RGB-D + エンコーダ積分)で作った署名の正答 6 / 6。 (figure labels are in Japanese; the numbers are the same)
↑ 手首ばねのたわみから推定した先端の力の比。
↑ μ = 0.8・θ₀ = 4.5° のくさび。
↑ くさびは力を抜いても引いても動かない(深さ 5.90 → 5.90 mm、内部圧縮)、詰まりは横の力を抜くと自重で進み(6.25 → 7.21 mm)引けば戻る(5.30 mm、力の向きの問題)—— Whitney の区別を MuJoCo で。
↑ The animation ―― 手首カメラ(640×480、0.1 s ごと、48 コマ): 降下 → くさびで止まる → ‘detected: wedging’ → 退避して傾きを戻す → 再降下で成功。
py -3.11 examples/poc_peg_failure_recovery.py
Source: examples/poc_peg_failure_recovery.py
This run produced 6 figures in total - see them all
Ops used (notes): chamfer_capture · failure_confusion · insertion_episode_summary · insertion_failure_classify · insertion_failure_presets · insertion_failure_table · insertion_failure_validate · insertion_recovery_primitive · insertion_signature · insertion_stall_detect · jamming_diagram · jamming_force_check · jamming_parallelogram_planar · peg_offset_from_rgbd · peg_params · peg_synthetic_rgbd · pegfail_scene_mjcf · text_box · tip_force_ratios · vision_boundary_flip · wedging_risk · whitney_clearance · wrist_load_from_deflection
| The physics-simulation × Fullseye series continues from tacsim (elastic membrane + camera, 2026.196). tacsim synthesised its own three-colour Lambertian images and inverted them itself — the truth and the subject were both home-made. This exhibit brings in real images (the calibration packs published under the MIT licence by the authors of arXiv:2109.04027), calibrates the forward map (normal → colour) and solves the inverse along two independent routes: a linear 12-parameter illumination I_c = a_c + l_c · n (inverted by photometric_stereo = the subject) and an example-based gradient LUT (tilt θ and azimuth φ cut into 125 × 125 bins, mean colour per bin, also a version with a quadratic in position). New module tacscalib, 10 ops, numpy only. Two truths come from outside: the known ball radius (inside the contact circle the membrane follows the sphere, so the normal is the closed form n = (−x, −y, √(R² − r²))/R) and hand-marked contact circles (integer pixels, radii in 2 px steps = a weak truth). R enters both calibration and evaluation, so the gates test whether the calibration generalises to other images, not R itself — calibrate on the 24 even frames, measure on the 24 odd ones. ★A dead band in the prototype was fixed: fitting the position quadratic only to well-populated bins dropped every low-tilt bin, and pixels with tilt 0–15° snapped to 0 or 15° (barely visible in the median angular error; only the profile plot showed it). Sparse bins now borrow the position terms of the angularly nearest polynomial bin and fit only their own constant — on the real hold-out, the median | Δθ | for true tilt < 15° drops from 4.84° to 1.89°. The price: the tilt floor on flat ground rises from 0.4° to 3.3° (the colour difference between a small tilt and flat sinks into the 3.6 DN background noise = a resolution limit). Single-row bins carry pure noise and bias the depth by +18.5 %; a minimum of 10 rows gives +6.0 %. ★The inverse lookup rewrites the distance as one feature inner product ( | k | ² = AᵀQA) and runs coarse → fine (pick the top two 3 × 3-bin blocks by their representatives): the same bin as brute force for 90.0 % of pixels, median angular error within 0.10°, 2.4× faster. 9 gates (synthetic, 1.3 s) plus 10 with –full (real data, 42 s): illumination round trip 4.8e-15, quantisation floor 0.430°, with ±40 % position gain 0.726° with position terms vs 4.600° without, dead band 13.24° → 0.58°, coefficients mapped in closed form to an external quadratic-LUT format agree to 7.1e-15 DN; real hold-out of 24 frames: RGB residual linear 7.92 DN (72-parameter position model 4.63, background noise 3.60), median angular error linear 10.71° / LUT without position 8.58° / LUT with position 4.17° / the external pre-calibrated LUT 3.57°*, contact_radius_ring (the subject) radius −0.25 px (−12.9 px with its default search radius), depth +4.7 % against 595 µm with a profile RMS of 7.1 µm, 7.59 / 8.83 / 3.70° for the position LUT with 1 / 4 / 24 calibration frames, and the second sensor’s linear residual 30.8 DN = 3.9× the first. Figures: a synthetic press with the error maps of three routes, a GIF circling the light (reconstruction vs the true spherical cap), the dead band before and after; with –full: a real frame → reconstruction → true cap, a real-data GIF, profiles, residual maps, error maps, the sensor-plane error map, the position scatter, the calibration-count curve and the radial tilt. All at 1:1. Honestly: there is no truth for force or indentation depth. Angular errors are for the nearest-bin LUT (no interpolation). *The external LUT was built from the same 48 frames, so it is a reference, not a hold-out. The second pack has no primary source for ball diameter or pixel pitch (only a code comment giving 0.0266 mm/px), so only radius-independent quantities (the ratio of linear residuals) are gated and the angular errors are ranked only. The position LUT breaks in the image corners (extrapolation where few calibration balls landed). Trap we hit: with all centres on integer pixels the synthetic data never produces sparse bins, so the dead band is invisible in principle (reproduced with sub-pixel offsets). Two issues in existing ops (contact_radius_ring’s default search radius cuts the circle at a quarter of the window; photometric.angular_error_deg’s 1.15e-4° floor) were fixed in a later commit (default search radius: −12.9 px → −0.25 px, and the default is now part of gate 12). |
| *↑ Calibrating a Vision-Based Tactile Sensor’s Illumination on Real Calibration Spheres, with a Gradient LUT as the Second Implementation — the Ball Radius and Hand-Marked Circles as the Truth ―― The physics-simulation × Fullseye series continues from tacsim (elastic membrane + camera, 2026.196). tacsim synthesised its own three-colour Lambertian images and inverted them itself — the truth and the subject were both home-made. This exhibit brings in real images (the calibration packs published under the MIT licence by the authors of arXiv:2109.04027), calibrates the forward map (normal → colour) and solves the inverse along two independent routes: a linear 12-parameter illumination I_c = a_c + l_c · n (inverted by photometric_stereo = the subject) and an example-based gradient LUT (tilt θ and azimuth φ cut into 125 × 125 bins, mean colour per bin, also a version with a quadratic in position). New module tacscalib, 10 ops, numpy only. Two truths come from outside: the known ball radius (inside the contact circle the membrane follows the sphere, so the normal is the closed form n = (−x, −y, √(R² − r²))/R) and hand-marked contact circles (integer pixels, radii in 2 px steps = a weak truth). R enters both calibration and evaluation, so the gates test whether the calibration generalises to other images, not R itself — calibrate on the 24 even frames, measure on the 24 odd ones. ★A dead band in the prototype was fixed: fitting the position quadratic only to well-populated bins dropped every low-tilt bin, and pixels with tilt 0–15° snapped to 0 or 15° (barely visible in the median angular error; only the profile plot showed it). Sparse bins now borrow the position terms of the angularly nearest polynomial bin and fit only their own constant — on the real hold-out, the median | Δθ | for true tilt < 15° drops from 4.84° to 1.89°. The price: the tilt floor on flat ground rises from 0.4° to 3.3° (the colour difference between a small tilt and flat sinks into the 3.6 DN background noise = a resolution limit). Single-row bins carry pure noise and bias the depth by +18.5 %; a minimum of 10 rows gives +6.0 %. ★The inverse lookup rewrites the distance as one feature inner product ( | k | ² = AᵀQA) and runs coarse → fine (pick the top two 3 × 3-bin blocks by their representatives): the same bin as brute force for 90.0 % of pixels, median angular error within 0.10°, 2.4× faster. 9 gates (synthetic, 1.3 s) plus 10 with –full (real data, 42 s): illumination round trip 4.8e-15, quantisation floor 0.430°, with ±40 % position gain 0.726° with position terms vs 4.600° without, dead band 13.24° → 0.58°, coefficients mapped in closed form to an external quadratic-LUT format agree to 7.1e-15 DN; real hold-out of 24 frames: RGB residual linear 7.92 DN (72-parameter position model 4.63, background noise 3.60), median angular error linear 10.71° / LUT without position 8.58° / LUT with position 4.17° / the external pre-calibrated LUT 3.57°, contact_radius_ring (the subject) radius −0.25 px (−12.9 px with its default search radius), depth +4.7 % against 595 µm with a profile RMS of 7.1 µm, 7.59 / 8.83 / 3.70° for the position LUT with 1 / 4 / 24 calibration frames, and the second sensor’s linear residual 30.8 DN = 3.9× the first. Figures: a synthetic press with the error maps of three routes, a GIF circling the light (reconstruction vs the true spherical cap), the dead band before and after; with –full: a real frame → reconstruction → true cap, a real-data GIF, profiles, residual maps, error maps, the sensor-plane error map, the position scatter, the calibration-count curve and the radial tilt. All at 1:1. Honestly: there is no truth for force or indentation depth. Angular errors are for the nearest-bin LUT (no interpolation). *The external LUT was built from the same 48 frames, so it is a reference, not a hold-out. The second pack has no primary source for ball diameter or pixel pitch (only a code comment giving 0.0266 mm/px), so only radius-independent quantities (the ratio of linear residuals) are gated and the angular errors are ranked only. The position LUT breaks in the image corners (extrapolation where few calibration balls landed). Trap we hit: with all centres on integer pixels the synthetic data never produces sparse bins, so the dead band is invisible in principle (reproduced with sub-pixel offsets). Two issues in existing ops (contact_radius_ring’s default search radius cuts the circle at a quarter of the window; photometric.angular_error_deg’s 1.15e-4° floor) were fixed in a later commit (default search radius: −12.9 px → −0.25 px, and the default is now part of gate 12). |
↑ The measurement ―― 位置つき LUT の法線を Frankot–Chellappa で積分した面に光を 1 周させる(中央)、右は真の球冠。等倍 89×89、24 コマ。 (figure labels are in Japanese; the numbers are the same)
↑ 合成の 1 球、真の傾き < 15° の画素。
↑ 法線の半径スロープを方位平均して 1D 積分した断面と真の球冠(破線)。
↑ 法線の角誤差 0〜20°(同じ hold-out 1 枚、等倍)。
↑ 接触中心の画像中心からの距離 vs 角誤差の中央値(hold-out 各 1 点)。
↑ The animation ―― 実機の 1 枚から復元した面に光を 1 周させる(中央)、右は真の球冠。等倍 201×201、24 コマ。
py -3.11 examples/poc_tacscalib_sphere_lut.py
Source: examples/poc_tacscalib_sphere_lut.py
This run produced 12 figures in total - see them all
Ops used (notes): calib_pack_load · contact_radius_ring · field_position_sweep · gradient_lut_build · gradient_lut_invert · integrate_normals · invert · lights_fit_from_sphere · median · membrane_predict_rgb · normal_error_map · normals_to_angles · poly_lut_invert · reflect · sphere_cap_height · sphere_normals_known · surface_normals · text_box
| The capstone of the physics-simulation × Fullseye series (pegsim 2026.195, pegfail 2026.201) and of the three vision-based tactile exhibits (tacsim, tacslip, tactorque). Elastic membranes are mounted on the two fingertips; the contact wrench the peg receives from the hole is recovered from the two membrane images alone, and Whitney’s contact states (none / chamfer / one point / two points, and wedging or jamming once stalled) are called by learning-free rules. In the same insertion the wrist stiffness k is identified from wrist-camera deflection × tactile force, and the symmetry of the peg shape (n-fold) and of the apparatus (two fingers are not rotationally symmetric) is measured by equivariance gates. New module pegtactile, 19 ops, plus 2 added to existing modules (tacsim.contact_radius_fit_pixelwise = contact radius without radial bins; tacslip.mindlin_fit_vector = a two-coefficient Mindlin fit for directed shear) and 4 mujoco facades. Ground truth comes from outside on four fronts. (1) The theorem: Whitney 1982 (paywalled and unread; the equations are from the author’s own MIT OCW 2.875 Class 3 slides): the two-point depth l₂(θ) = (2R − r(cosθ + secθ))/tanθ and the wedging boundary θ = c/μ. (2) Contact-mechanics closed forms (Johnson 1985): Hertz, Cattaneo–Mindlin, Cerruti, no-slip torsion and the full-slip torque (since 2026-10-06 torsion is synthesised and read with a numerical partial-slip solution whose two limits are these; the 0.4.0 no-slip reading over-reads partial-slip images by +33 % at half the full-slip torque and the gates missed it because the synthesis was no-slip too — gate 2b). (3) Group actions (rotate the shape or the scene and the answer rotates by the same amount). (4) MuJoCo’s contact points and forces, the wrist force/torque sensor and the set wrist-spring values (–full). MuJoCo has no membrane, so the pad loads are a statics map that distributes the exposure-averaged contact wrench plus gravity to the two pads. ★The wedging boundary is the blind spot of a wrench-only rule: where the slope c/θ of the line from mouth to tip equals μ, the edge of the tip’s friction cone passes through the mouth contact and a two-point wrench is indistinguishable from a single mouth contact (2 of 36 closed-form cases, θ 3°, μ 0.8). Adding Whitney’s geometry (force two-point when depth ≥ W + l₂(θ)) gives 36/36. ★Rotating the scene by 90° moves the tilting moment from a shear couple to pad torsion: reading torsion keeps the agreement at 97.6 → 96.8 %; dropping it gives 47.6 %. Figures: the two membrane images and their readings, a GIF of Whitney’s closed-form insertion read through the membranes, the radial-bin trap, the wrench-only blind spot, shape orientation (Fourier phase vs second moments); with –full: an insertion GIF (hole cross-section, wrist camera, both membranes, state band), time series of four runs, wrist stiffness F = kΔx, the two-point onset, and the agreement after a 90° rotation. 11 gates (numpy, 3.5 s) plus 12 with –full (3 heavy numpy + 9 MuJoCo): statics round trip 4.4e-16; membrane round trip P 0.011 %, q 2.8 mN (4.4 mN over 20 cases), torsion 0.015 mN·m, direction 0.12°; false shear at zero load 12.1 mN on a regular marker grid → 0.7 mN with jitter; slope error of P̂ 8.5 % with bins → 0.018 % per pixel; Whitney’s 36 cases 34 with the wrench only → 36 with geometry; two-point μ within 0.0008 (normal force 0.6 N); n-fold symmetry 0/3/4/6/1; Fourier-phase orientation 0.001–0.018° (keyed 0.59°) vs second moments off by 10–59°; wrist RGB-D equivariance 3.2 µm (0.000 µm under the mirror); MuJoCo: map vs force sensor 0.41 mN and 0.023 mN·m, tactile wrench | ΔF | median 2.7 / max 7.0 mN, state agreement 97.7 % (98.2 % with the true wrench), two-point onset within 0.04 mm of the closed form, wall μ 0.292 from one-point and 0.2998 from two-point sliding (set 0.3), wedging / jamming 2/2, wrist camera 1.2 µm, k_t 599.9–601.1 N/m (set 600), k_r 1.496–1.512 N·m/rad (set 1.5). Honestly: MuJoCo has no membrane, so the pad loads come from a statics map (symmetric left/right split assumed, peg inertia ignored — 11.7 mN vertical lies outside it). Synthesis and inversion use the same closed-form family, so errors of the membrane model (finite thickness, large deformation) are invisible to these gates (tacslip’s finite-element comparison covers that). k is identified only in two-point intervals. Each condition is run once. There is no MuJoCo insertion of square or hexagonal pegs (symmetry is gated on the perception ops only). Traps we hit: pixel locking of a regular marker grid, radial-bin contact radii that skew the slope of P̂ by 40 % and k by +3.7 % through the two-pad difference, instantaneous contact forces are impulses (averaged over the exposure), and without pruning the candidate points a two-point line of action reads as one point. |
| *↑ Reading Peg-in-Hole Insertion through Two Fingertip Membranes — Contact Wrench, Whitney’s Contact States, Wall Friction and Wrist Stiffness from Shear Images; at the Wedging Boundary the Wrench Alone Is Blind ―― The capstone of the physics-simulation × Fullseye series (pegsim 2026.195, pegfail 2026.201) and of the three vision-based tactile exhibits (tacsim, tacslip, tactorque). Elastic membranes are mounted on the two fingertips; the contact wrench the peg receives from the hole is recovered from the two membrane images alone, and Whitney’s contact states (none / chamfer / one point / two points, and wedging or jamming once stalled) are called by learning-free rules. In the same insertion the wrist stiffness k is identified from wrist-camera deflection × tactile force, and the symmetry of the peg shape (n-fold) and of the apparatus (two fingers are not rotationally symmetric) is measured by equivariance gates. New module pegtactile, 19 ops, plus 2 added to existing modules (tacsim.contact_radius_fit_pixelwise = contact radius without radial bins; tacslip.mindlin_fit_vector = a two-coefficient Mindlin fit for directed shear) and 4 mujoco facades. Ground truth comes from outside on four fronts. (1) The theorem: Whitney 1982 (paywalled and unread; the equations are from the author’s own MIT OCW 2.875 Class 3 slides): the two-point depth l₂(θ) = (2R − r(cosθ + secθ))/tanθ and the wedging boundary θ = c/μ. (2) Contact-mechanics closed forms (Johnson 1985): Hertz, Cattaneo–Mindlin, Cerruti, no-slip torsion and the full-slip torque (since 2026-10-06 torsion is synthesised and read with a numerical partial-slip solution whose two limits are these; the 0.4.0 no-slip reading over-reads partial-slip images by +33 % at half the full-slip torque and the gates missed it because the synthesis was no-slip too — gate 2b). (3) Group actions (rotate the shape or the scene and the answer rotates by the same amount). (4) MuJoCo’s contact points and forces, the wrist force/torque sensor and the set wrist-spring values (–full). MuJoCo has no membrane, so the pad loads are a statics map that distributes the exposure-averaged contact wrench plus gravity to the two pads. ★The wedging boundary is the blind spot of a wrench-only rule: where the slope c/θ of the line from mouth to tip equals μ, the edge of the tip’s friction cone passes through the mouth contact and a two-point wrench is indistinguishable from a single mouth contact (2 of 36 closed-form cases, θ 3°, μ 0.8). Adding Whitney’s geometry (force two-point when depth ≥ W + l₂(θ)) gives 36/36. ★Rotating the scene by 90° moves the tilting moment from a shear couple to pad torsion: reading torsion keeps the agreement at 97.6 → 96.8 %; dropping it gives 47.6 %. Figures: the two membrane images and their readings, a GIF of Whitney’s closed-form insertion read through the membranes, the radial-bin trap, the wrench-only blind spot, shape orientation (Fourier phase vs second moments); with –full: an insertion GIF (hole cross-section, wrist camera, both membranes, state band), time series of four runs, wrist stiffness F = kΔx, the two-point onset, and the agreement after a 90° rotation. 11 gates (numpy, 3.5 s) plus 12 with –full (3 heavy numpy + 9 MuJoCo): statics round trip 4.4e-16; membrane round trip P 0.011 %, q 2.8 mN (4.4 mN over 20 cases), torsion 0.015 mN·m, direction 0.12°; false shear at zero load 12.1 mN on a regular marker grid → 0.7 mN with jitter; slope error of P̂ 8.5 % with bins → 0.018 % per pixel; Whitney’s 36 cases 34 with the wrench only → 36 with geometry; two-point μ within 0.0008 (normal force 0.6 N); n-fold symmetry 0/3/4/6/1; Fourier-phase orientation 0.001–0.018° (keyed 0.59°) vs second moments off by 10–59°; wrist RGB-D equivariance 3.2 µm (0.000 µm under the mirror); MuJoCo: map vs force sensor 0.41 mN and 0.023 mN·m, tactile wrench | ΔF | median 2.7 / max 7.0 mN, state agreement 97.7 % (98.2 % with the true wrench), two-point onset within 0.04 mm of the closed form, wall μ 0.292 from one-point and 0.2998 from two-point sliding (set 0.3), wedging / jamming 2/2, wrist camera 1.2 µm, k_t 599.9–601.1 N/m (set 600), k_r 1.496–1.512 N·m/rad (set 1.5). Honestly: MuJoCo has no membrane, so the pad loads come from a statics map (symmetric left/right split assumed, peg inertia ignored — 11.7 mN vertical lies outside it). Synthesis and inversion use the same closed-form family, so errors of the membrane model (finite thickness, large deformation) are invisible to these gates (tacslip’s finite-element comparison covers that). k is identified only in two-point intervals. Each condition is run once. There is no MuJoCo insertion of square or hexagonal pegs (symmetry is gated on the perception ops only). Traps we hit: pixel locking of a regular marker grid, radial-bin contact radii that skew the slope of P̂ by 40 % and k by +3.7 % through the two-pad difference, instantaneous contact forces are impulses (averaged over the exposure), and without pruning the candidate points a two-point line of action reads as one point.* |
↑ The measurement ―― Whitney の二点接触(θ 3°、μ 0.8 = くさびの境目の近く)を 2 本の指の膜で見た像(等倍 256 px = 16 mm)。矢印はマーカーの変位 ×12、円は読んだ接触円。真値の荷重 L: P 4.093 N・q (-0.000, 0.344) N、R: P 3.907 N・q (-0.000, 0.398) N。左右でせん断の v 成分が逆向き = モーメントは偶力で運ばれる。 (figure labels are in Japanese; the numbers are the same)
↑ 同じ二点接触の接触レンチ。
↑ 二点接触(閉形式)をレンチだけの規則に通すと、口と先端を結ぶ線の傾き c/θ が μ に等しい線(破線、Whitney のくさびの境目そのもの)の近くでだけ口の一点と読む(12 組中 2 組)。
↑ 走行 B の接触力の大きさと把持点まわりの M_y(破線 = MuJoCo の露光平均、実線 = 膜 2 枚から)。
↑ 走行 D の接触力の大きさと把持点まわりの M_y(破線 = MuJoCo の露光平均、実線 = 膜 2 枚から)。
↑ The animation ―― Whitney の閉形式の挿入(θ 3°、μ 0.3)を膜 2 枚で読む動く図: 先端が壁に一点で触れたまま深くなり、深さ W + l₂(θ) = 8.63 mm で胴が口の縁にも触れて二点へ。上の文字の色 = 膜からの判定が真値と一致(全 14 コマ)。
↑ The animation ―― 挿入の動く図(θ 3°、横ずれ 0.6 mm、48 コマ = 0.08 s ごと): 左 = 穴の断面(板を半透明、緑の円 = MuJoCo の接触点、大きさ ∝ 力、桃の × = 膜 2 枚から読んだ接触点)、中 = 手首カメラ、右 = 2 本の指の膜(矢印 ×12)。下の帯 = 状態(灰 無接触・青 面取り・橙 一点・赤 二点)の MuJoCo と触覚。
py -3.11 examples/poc_peg_insertion_tactile.py
Source: examples/poc_peg_insertion_tactile.py
This run produced 14 figures in total - see them all
Ops used (notes): arrow · blob_features · blob_label · contact_radius_fit · contact_radius_fit_pixelwise · contact_state_from_wrench · ellipse · equivariance_check · friction_from_single_contact · hertz_force · hertz_sphere · membrane_recover · pad_context · pad_loads_to_peg_wrench · pad_params · pad_tactile_frame · pad_tactile_read · peg_offset_from_rgbd · peg_params · peg_synthetic_rgbd · peg_wrench_to_pad_loads · project_points · stall_verdict · symmetric_peg_shape … (+8)
The physics-simulation × Fullseye series continues (soft-wrist peg insertion, failure classification, two-finger membranes). The subject is research that uses symmetry for learning (Symmetry-aware RL, ICRA 2024, arXiv:2402.18002; the abstract was read as the primary source); here the group identities themselves are the gates, building the parts that remain without learning. Regular n-gon prisms (n = 3, 4, 6) and a keyed square with one corner cut by 1.8 mm (symmetry n = 1) are inserted into holes of the same shape offset outward by 0.2 mm (45° chamfer, 0.5 mm). New module pegsym, 15 ops plus 6 mujoco facades. (1) Reading orientation: images are rectified to a top view of the plane (the hole mouth from the wrist camera, the peg end face from an upward camera), and the complex Fourier phase of the contour gives the angle modulo 2π/n. Second moments are isotropic for n ≥ 3 and carry no orientation (a trap). Synthetic equivariance over 40 angles per period: 0.054° for regular polygons, 0.19° keyed; from MuJoCo images the relative angle is within 0.05° (regular) and 0.70° keyed (bias from a hole that is not similar). (2) Rotation window (derived): φ = π/n − arccos((A + δ + W)cos(π/n)/A) (written with atan2) matches a linear program with free translation (second implementation) to 2.8e-11 rad; windows are 4.75° triangle, 8.72° square, 20.85° hexagon. (3) The friction limit (derived, found in MuJoCo): only the hexagon captured just 7.25° in MuJoCo. For a vertex resting on the mouth to slide down the chamfer the peg must turn, and the moment balance about the axis says it turns only if sin²β > (√(1 + 8μ²) − 1)/2. Within 0.5° of MuJoCo at six points μ = 0–0.5 (μ = 0.3: closed form 6.76°, MuJoCo 7.25°). (4) Number of tries: the closed-form expectation of sweeping one period with steps at most twice the window agrees within 3 % with an error grid judged by a linear program on the mouth (triangle 6.97 / 6.96, square 3.36 / 3.29, hexagon 1.31 / 1.29, keyed 10.98 / 11.02). Blind searches in MuJoCo (12 hole orientations × 4 shapes) match the predicted tries in 47 / 48, with means of 3.50 (square) and 11.00 (keyed). Using the image reading as the estimate inserts 16 / 16 on the first try; rebuilding the hole rotated by an extra 2πk/n gives the same tries (6 / 6). (5) Two-point contact of polygons: no closed form was found, so an exact solution was built from a linear program on convex-hull vertices plus bisection. A square tilted about an axis parallel to a face matches Whitney’s cylinder formula to 3.4e-11 m and the small-angle limit of eq. (2.28) of Goli et al. 2024 (R. Soc. Open Sci.) to 1.00000; a second implementation with MuJoCo’s distance function agrees within 6.5 µm of lateral clearance. (6) Spiral: at first the search skipped turns near the centre and left 64 / 11,285 holes in the cover → arc length is now accumulated on a fine grid. With steps meeting the sufficient condition there are no holes, and the expected count is within 3.6 % of the swept-area approximation. Figures: a GIF of three rotating shapes and their readings, tries vs symmetry, window margin, two-point depth vs tilt direction, spiral cover; with –full: a GIF of the MuJoCo blind search, window vs friction, and the rectified wrist and upward views. 10 gates (default, 1.8 s) plus 7 with –full (42 s with figures). Honestly: the peg orientation is read by a fixed upward camera mid-process (the wrist camera alone cannot see the end face). The friction limit is derived for a 45° chamfer, quasi-statics and a weak spring about the axis; for the square at μ ≥ 0.5 MuJoCo is 0.7° wider (shown as a breakdown, not gated). Sturges’s square-peg wedging was not read. The nested count for searching translation and rotation together is an independence approximation and is not gated.
↑ Cutting a Peg’s Rotation Search to 1/n with Its Symmetry — Inserting Square, Hexagonal and Keyed Pegs Checked by Group Identities, Closed Forms and MuJoCo; Friction Shrinks the Hexagon’s Chamfer Window from 20.9° to 6.8° ―― The physics-simulation × Fullseye series continues (soft-wrist peg insertion, failure classification, two-finger membranes). The subject is research that uses symmetry for learning (Symmetry-aware RL, ICRA 2024, arXiv:2402.18002; the abstract was read as the primary source); here the group identities themselves are the gates, building the parts that remain without learning. Regular n-gon prisms (n = 3, 4, 6) and a keyed square with one corner cut by 1.8 mm (symmetry n = 1) are inserted into holes of the same shape offset outward by 0.2 mm (45° chamfer, 0.5 mm). New module pegsym, 15 ops plus 6 mujoco facades. (1) Reading orientation: images are rectified to a top view of the plane (the hole mouth from the wrist camera, the peg end face from an upward camera), and the complex Fourier phase of the contour gives the angle modulo 2π/n. Second moments are isotropic for n ≥ 3 and carry no orientation (a trap). Synthetic equivariance over 40 angles per period: 0.054° for regular polygons, 0.19° keyed; from MuJoCo images the relative angle is within 0.05° (regular) and 0.70° keyed (bias from a hole that is not similar). (2) Rotation window (derived): φ = π/n − arccos((A + δ + W)cos(π/n)/A) (written with atan2) matches a linear program with free translation (second implementation) to 2.8e-11 rad; windows are 4.75° triangle, 8.72° square, 20.85° hexagon. (3) The friction limit (derived, found in MuJoCo): only the hexagon captured just 7.25° in MuJoCo. For a vertex resting on the mouth to slide down the chamfer the peg must turn, and the moment balance about the axis says it turns only if sin²β > (√(1 + 8μ²) − 1)/2. Within 0.5° of MuJoCo at six points μ = 0–0.5 (μ = 0.3: closed form 6.76°, MuJoCo 7.25°). (4) Number of tries: the closed-form expectation of sweeping one period with steps at most twice the window agrees within 3 % with an error grid judged by a linear program on the mouth (triangle 6.97 / 6.96, square 3.36 / 3.29, hexagon 1.31 / 1.29, keyed 10.98 / 11.02). Blind searches in MuJoCo (12 hole orientations × 4 shapes) match the predicted tries in 47 / 48, with means of 3.50 (square) and 11.00 (keyed). Using the image reading as the estimate inserts 16 / 16 on the first try; rebuilding the hole rotated by an extra 2πk/n gives the same tries (6 / 6). (5) Two-point contact of polygons: no closed form was found, so an exact solution was built from a linear program on convex-hull vertices plus bisection. A square tilted about an axis parallel to a face matches Whitney’s cylinder formula to 3.4e-11 m and the small-angle limit of eq. (2.28) of Goli et al. 2024 (R. Soc. Open Sci.) to 1.00000; a second implementation with MuJoCo’s distance function agrees within 6.5 µm of lateral clearance. (6) Spiral: at first the search skipped turns near the centre and left 64 / 11,285 holes in the cover → arc length is now accumulated on a fine grid. With steps meeting the sufficient condition there are no holes, and the expected count is within 3.6 % of the swept-area approximation. Figures: a GIF of three rotating shapes and their readings, tries vs symmetry, window margin, two-point depth vs tilt direction, spiral cover; with –full: a GIF of the MuJoCo blind search, window vs friction, and the rectified wrist and upward views. 10 gates (default, 1.8 s) plus 7 with –full (42 s with figures). Honestly: the peg orientation is read by a fixed upward camera mid-process (the wrist camera alone cannot see the end face). The friction limit is derived for a 45° chamfer, quasi-statics and a weak spring about the axis; for the square at μ ≥ 0.5 MuJoCo is 0.7° wider (shown as a breakdown, not gated). Sturges’s square-peg wedging was not read. The nested count for searching translation and rotation together is an independence approximation and is not gated.
↑ The measurement ―― 線 = 窓 5° を共通にした時の閉形式 E(n)、破線 = 対称を知らない探索の E を n で割ったもの。点 = この装置(窓は形ごとに違う)の閉形式と、一様な誤差の格子で口への線形計画が判定した平均。キー付きは n = 1。 (figure labels are in Japanese; the numbers are the same)
↑ 平行移動も自由にした時の口の中の最良の余裕(線形計画)。
↑ 傾き 3° の二点接触の深さ(厳密、凸包の頂点の線形計画)を内接円の円柱の Whitney の式で割ったもの。
↑ Archimedes のらせん。
↑ 面取りが回して入れる最大の向きの誤差。
↑ The animation ―― 正方形のペグの盲目の回転探索(MuJoCo、斜め上の固定カメラ)。候補は 90° の 1 周期を 6 等分(刻み 15.0°、面取りの窓 ±8.72°)。下の帯 = 試行(赤 = 面取りか襟で止まった、緑 = 入った)。6 回目で入り、予言も 6 回。
py -3.11 examples/poc_peg_symmetry_search.py
Source: examples/poc_peg_symmetry_search.py
This run produced 8 figures in total - see them all
Ops used (notes): pegsym_scene_mjcf · plane_topview · polygon_coverage_image · polygon_fit_check · polygon_offset · polygon_peg · polygon_two_point_depth · polygon_yaw_read · relative_yaw_from_images · rotation_search_plan · rotation_window · search_expected_tries · spiral_expected_tries · spiral_search_points · symmetry_fold
| The same 200,000-point cloud, with only the point order shuffled, is fed to Kabsch. An ordinary FP64 matrix product (DGEMM) moves its low bits with the summation order, so 11 shuffles gave 12 different rotations (largest difference 1.7e-15). The new module ozakimm (6 ops) splits the reduction into exact integer partial products (Ozaki-I = fixed-point slices of 7 bits, Ozaki-II = Chinese remainder theorem) and returns one set of bits regardless of point order or BLAS thread count 1/2/4/8. Each extra slice drops the error by about 2⁻⁷ until it reaches the FP64 level (the reference is the exact product computed with Python integers), and the slice count is chosen from a guaranteed error bound (9 / 11 / 13 slices for φ = 0.5 / 2 / 4). The bound is never violated over 9,216 elements. Scaling the input by a power of two scales the answer exactly, so the defect reported in arXiv 2606.29129 for the old fast mode — the answer breaking under a mere constant factor — is absent. The ICP family (five functions in registration / match3d / gicp) gained reproducible=True: aligning a 20,000-point wavy surface in 5 point orders gives 5 different poses by default for every one of them and a single pose with reproducible=True (difference from the default | ΔR | ≤ 3.3e-15; the whole ICP is 2-5 times slower, so the default stays as it was). Honestly: it is not faster on the CPU (11-60 times slower than DGEMM for square matrices; 0.68 ms → 170 ms for the 3×3 cross-covariance of 200,000 points). To make FP64 fast on a GPU, cuBLAS 13.4 FP64 emulation (opt-in) reached 17.8-23.8 TFLOPS at n = 8192 on one local machine (native 1.67; 13.3 fell back to native for inputs with a wide exponent span). cuBLAS does not guarantee bitwise reproducibility while emulating, so: speed from cuBLAS, reproducibility from here. |
| *↑ Registration That Does Not Move by a Single Bit When the Points Are Shuffled — FP64 Reductions by the Ozaki Scheme, with the Error Falling in Steps under a Guaranteed Bound; Slow on the CPU, and GPU Speed Is Left to cuBLAS Emulation ―― The same 200,000-point cloud, with only the point order shuffled, is fed to Kabsch. An ordinary FP64 matrix product (DGEMM) moves its low bits with the summation order, so 11 shuffles gave 12 different rotations (largest difference 1.7e-15). The new module ozakimm (6 ops) splits the reduction into exact integer partial products (Ozaki-I = fixed-point slices of 7 bits, Ozaki-II = Chinese remainder theorem) and returns one set of bits regardless of point order or BLAS thread count 1/2/4/8. Each extra slice drops the error by about 2⁻⁷ until it reaches the FP64 level (the reference is the exact product computed with Python integers), and the slice count is chosen from a guaranteed error bound (9 / 11 / 13 slices for φ = 0.5 / 2 / 4). The bound is never violated over 9,216 elements. Scaling the input by a power of two scales the answer exactly, so the defect reported in arXiv 2606.29129 for the old fast mode — the answer breaking under a mere constant factor — is absent. The ICP family (five functions in registration / match3d / gicp) gained reproducible=True: aligning a 20,000-point wavy surface in 5 point orders gives 5 different poses by default for every one of them and a single pose with reproducible=True (difference from the default | ΔR | ≤ 3.3e-15; the whole ICP is 2-5 times slower, so the default stays as it was). Honestly: it is not faster on the CPU (11-60 times slower than DGEMM for square matrices; 0.68 ms → 170 ms for the 3×3 cross-covariance of 200,000 points). To make FP64 fast on a GPU, cuBLAS 13.4 FP64 emulation (opt-in) reached 17.8-23.8 TFLOPS at n = 8192 on one local machine (native 1.67; 13.3 fell back to native for inputs with a wide exponent span). cuBLAS does not guarantee bitwise reproducibility while emulating, so: speed from cuBLAS, reproducibility from here.* |
↑ The measurement ―― Ozaki-II は積の回数 = 法の数。φ が広いと同じ法の数では FP64 に届かない(φ=4 で 20 法 4.9e-16)。 (figure labels are in Japanese; the numbers are the same)
↑ 点の順を入れ替えるだけで、普通の FP64 の回転は 12 通りに分かれた(最大の差 1.7e-15)。
↑ 違う結果の数(1 = どの条件でも同じビット)。
↑ 手元の 1 台での測定(2026-10-05)。
↑ 手元の 1 台での測定(2026-10-05)。
py -3.11 examples/poc_reproducible_icp.py
Source: examples/poc_reproducible_icp.py
This run produced 7 figures in total - see them all
Ops used (notes): cross_covariance_reproducible · estimate_normals · fp64_emulation_probe · gicp · icp_point2plane · icp_point2point_3d · kabsch_reproducible · matmul_ozaki · matmul_reproducible · ozaki_error_bound
Public fixed cameras (road, weather, tourism) publish their position but not their orientation, or only a coarse one (a road direction code, an id hash, a manual gizmo); without the orientation a frame cannot be placed on a map, a DEM or a 3-D city. The new geocam family (7 ops, numpy + scipy) recovers the (yaw, pitch, roll) of a camera at a known position from the picture itself, with no learning, from two independent cues that check each other. (1) Skyline: the 360-degree ridge rendered from a DEM at the camera position (dem_skyline, with earth curvature and refraction and the horizon dip as the floor outside the DEM) is matched against the sky/terrain boundary extracted by dynamic programming (skyline_extract, Lie et al. 2005); camera_orientation_from_skyline returns the whole residual curve over yaw and the margin to the runner-up valley. (2) Sun: the apparent sun position is a closed form of time and place (sun_position, NOAA, checked against equinox and solstice values); two or more saturated sun discs picked from time-stamped frames (sun_pixel_position, which refuses clouds and sky bands by their fill ratio) fix the rotation exactly as the SVD solution of Wahba’s problem (camera_orientation_from_sun). On a synthetic camera with a known pose (synthetic DEM, sky, a cloud, a foreground pole, noise, known intrinsics) the skyline route errs by 0.058 / 0.030 / 0.006 deg, the sun route by 0.002 / 0.001 / 0.009 deg (5 frames; the first and last frame alone give a yaw error of 0.001 deg), and the two routes agree to 0.061 deg. Controls: a road-direction prior of the kind public metadata gives is off by 7.5 deg, and on a flat DEM the ridge is the same in every direction so the op returns ambiguous instead of being silently 137 deg wrong. Prior work: Lalonde et al. IJCV 2010 (sun and sky, 3 deg on 22 webcams) and Baatz et al. ECCV 2012 (skylines, the large-scale position-unknown version). Honest breakdown: synthetic only (real data - Fintraffic weather cameras + NLS elevation in Finland, Statens vegvesen + Kartverket DTM10 in Norway - is the next step and raw frames are never committed), the intrinsics K are required (an error in K turns into pitch and roll), skylines need mountains and the sun needs to be in the picture.
↑ Where Is the Public Camera Looking — The Orientation of a Fixed Camera Whose Only Published Fact Is Its Position, from the Picture Itself ―― Public fixed cameras (road, weather, tourism) publish their position but not their orientation, or only a coarse one (a road direction code, an id hash, a manual gizmo); without the orientation a frame cannot be placed on a map, a DEM or a 3-D city. The new geocam family (7 ops, numpy + scipy) recovers the (yaw, pitch, roll) of a camera at a known position from the picture itself, with no learning, from two independent cues that check each other. (1) Skyline: the 360-degree ridge rendered from a DEM at the camera position (dem_skyline, with earth curvature and refraction and the horizon dip as the floor outside the DEM) is matched against the sky/terrain boundary extracted by dynamic programming (skyline_extract, Lie et al. 2005); camera_orientation_from_skyline returns the whole residual curve over yaw and the margin to the runner-up valley. (2) Sun: the apparent sun position is a closed form of time and place (sun_position, NOAA, checked against equinox and solstice values); two or more saturated sun discs picked from time-stamped frames (sun_pixel_position, which refuses clouds and sky bands by their fill ratio) fix the rotation exactly as the SVD solution of Wahba’s problem (camera_orientation_from_sun). On a synthetic camera with a known pose (synthetic DEM, sky, a cloud, a foreground pole, noise, known intrinsics) the skyline route errs by 0.058 / 0.030 / 0.006 deg, the sun route by 0.002 / 0.001 / 0.009 deg (5 frames; the first and last frame alone give a yaw error of 0.001 deg), and the two routes agree to 0.061 deg. Controls: a road-direction prior of the kind public metadata gives is off by 7.5 deg, and on a flat DEM the ridge is the same in every direction so the op returns ambiguous instead of being silently 137 deg wrong. Prior work: Lalonde et al. IJCV 2010 (sun and sky, 3 deg on 22 webcams) and Baatz et al. ECCV 2012 (skylines, the large-scale position-unknown version). Honest breakdown: synthetic only (real data - Fintraffic weather cameras + NLS elevation in Finland, Statens vegvesen + Kartverket DTM10 in Norway - is the next step and raw frames are never committed), the intrinsics K are required (an error in K turns into pitch and roll), skylines need mountains and the sun needs to be in the picture.
↑ The measurement ―― the DEM ridge drawn at the estimated yaw / pitch / roll lies on the extracted skyline; errors 0.06 / 0.03 / 0.01 deg (figure labels are in Japanese; the numbers are the same)
↑ the op returns the whole residual curve so that the ambiguity is visible: a runner-up valley at 15 deg is 1.28 deg worse; the flat DEM curve is level
↑ the sun’s path over the day where it is above the ridge (yellow, projected with the pose estimated from the sun) and the 5 sun discs picked by sun_pix…
↑ both routes recover the pose to well under a degree and agree with each other; the public-metadata prior is off by ten degrees and the flat-ground cas…
py -3.11 examples/poc_public_camera_heading.py
Source: examples/poc_public_camera_heading.py
This run produced 5 figures in total - see them all
Ops used (notes): camera_orientation_from_skyline · camera_orientation_from_sun · dem_skyline · project · render_skyline_view · skyline_extract · sun_pixel_position · sun_position
| The synthetic PoC (poc_public_camera_heading) got away with ‘the sun is a small saturated disc’; real road cameras did not. Probing the 24 hours of 20-21 September from Finland’s 807 Fintraffic weather cameras (CC BY 4.0, no key) at sun elevations of 2-12 degrees, the look-alone gate (sun_pixel_position) called station-name text, signs, white cars and lens droplets the sun (24/24 wrong on the first probe), the sun is not a disc but a bloom whose size depends on exposure and which the station-name band clips, and the cameras look down at the road with sky in the top third only. Two ops were added to geocam: sun_bloom_fit (a Kasa circle fitted to the unclipped rim of the largest saturated blob; the centroid sits 12 px off on average, away from the cut) and camera_orientation_from_sun_candidates (RANSAC over per-frame candidates for the one blob that moves at the sun’s rate in a fixed camera, with road-camera priors | roll | <= 12 deg, pitch -40..0 deg, HFOV 25-120 deg rejecting unphysical hypotheses, the focal length searched at the same time, and no votes from below-horizon times). Over 807 stations x 24 h exactly one sunset could be tracked (E18, Hamina; official metadata says direction UNKNOWN): from 5 frames (15:01-16:01 UTC) yaw 267.9, pitch -4.6, roll 0.9 deg, f 1439 px (HFOV 48 deg), residual 0.34 deg. Independent check: the lane vanishing point (medoid over several daytime frames) turned into a world bearing with the same pose is 272.7 deg against 275.3 deg for the E18 segment in OpenStreetMap - 2.5 deg apart. The weak degrees of freedom are not hidden: under leave-one-out yaw moves 1.3 deg but roll 10 deg and f 2 % (the limit of a one-hour low-elevation arc). On the same frames the look-alone gate said ‘sun’ 22 times: 3 exact, 2 on the sun but off-centre, 17 something else. Raw frames are never committed; only the aggregate (times, circle fits, vanishing point, road bearing) ships. Sources: Fintraffic / Digitraffic (CC BY 4.0), OpenStreetMap (ODbL). |
| *↑ Where Is the Public Camera Looking, for Real — Hunting the Sun in 807 Road Cameras, Fixing the Heading from One Sunset and Checking It Against the Road ―― The synthetic PoC (poc_public_camera_heading) got away with ‘the sun is a small saturated disc’; real road cameras did not. Probing the 24 hours of 20-21 September from Finland’s 807 Fintraffic weather cameras (CC BY 4.0, no key) at sun elevations of 2-12 degrees, the look-alone gate (sun_pixel_position) called station-name text, signs, white cars and lens droplets the sun (24/24 wrong on the first probe), the sun is not a disc but a bloom whose size depends on exposure and which the station-name band clips, and the cameras look down at the road with sky in the top third only. Two ops were added to geocam: sun_bloom_fit (a Kasa circle fitted to the unclipped rim of the largest saturated blob; the centroid sits 12 px off on average, away from the cut) and camera_orientation_from_sun_candidates (RANSAC over per-frame candidates for the one blob that moves at the sun’s rate in a fixed camera, with road-camera priors | roll | <= 12 deg, pitch -40..0 deg, HFOV 25-120 deg rejecting unphysical hypotheses, the focal length searched at the same time, and no votes from below-horizon times). Over 807 stations x 24 h exactly one sunset could be tracked (E18, Hamina; official metadata says direction UNKNOWN): from 5 frames (15:01-16:01 UTC) yaw 267.9, pitch -4.6, roll 0.9 deg, f 1439 px (HFOV 48 deg), residual 0.34 deg. Independent check: the lane vanishing point (medoid over several daytime frames) turned into a world bearing with the same pose is 272.7 deg against 275.3 deg for the E18 segment in OpenStreetMap - 2.5 deg apart. The weak degrees of freedom are not hidden: under leave-one-out yaw moves 1.3 deg but roll 10 deg and f 2 % (the limit of a one-hour low-elevation arc). On the same frames the look-alone gate said ‘sun’ 22 times: 3 exact, 2 on the sun but off-centre, 17 something else. Raw frames are never committed; only the aggregate (times, circle fits, vanishing point, road bearing) ships. Sources: Fintraffic / Digitraffic (CC BY 4.0), OpenStreetMap (ODbL).* |
↑ The measurement ―― the sunset seen by C0362200 (E18, Hamina, Finland; metadata says direction UNKNOWN): the sun’s path for the evening drawn from the fitted pose (magenta) and the 5 blooms fitted with a circle (cyan); yaw 267.9, pitch -4.6, roll 0.9, HFOV 48 (figure labels are in Japanese; the numbers are the same)
↑ what the look-alone detector (sun_pixel_position, built for synthetic discs) called the sun in the same camera: 22 picks, 3 exactly the sun, 2 on the…
↑ refit with each sun frame removed: yaw barely moves, roll and the focal length are the weak directions of a 1-hour low-elevation arc — this is why the…
↑ for blooms cut by the station-name band the centroid sits 12.4 px (mean) away from the circle fitted to the unclipped rim; the fit uses the circle
↑ one camera out of 807 stations gave a sun track of 4 or more frames on 2026-09-20/21 (2 more had 3 frames and were left out); the vanishing point of i…
py -3.11 examples/poc_public_camera_heading_real.py
Source: examples/poc_public_camera_heading_real.py
This run produced 6 figures in total - see them all
Ops used (notes): bloom · camera_orientation_from_sun_candidates · ellipse · project · sun_position
Fit the coefficients of a thin-film forward model to real images, and give no number to a coefficient the data do not determine (sim-to-real calibration with the simcal ops, planned and screened with multivar). Model: transfer matrix (filmoptics) x black-body illuminant (spectralrad) x CIE 1931 to sRGB x gain, gamma and vignetting. Planning: seven knobs on an L18 array; Morris screening puts illumination x gain into one combination (sensitivity ratio 1.0000) and a 500 nm film seen at one wavelength fixes only A^1 d^0.93 (optical path n d); for the 98 nm film the interface reflectance dominates and the exponent departs from n d, stated. Core, public measured data (Schwarz et al. 2024, CC BY 4.0): fitting thickness, Cauchy A and B, NA and gain to the 100 % coverage microspectroscopy gives no point estimate (local collinearity gamma > 10) but a 95 % profile-likelihood interval of [94.6, 99.0] nm (width 4.4 nm) that contains the paper’s 98.5 nm reference; the noise comes from a separate measurement (bare silicon) and all six over-fitting gates pass on held-out wavelengths. The normal-incidence model with the table index gives a narrow [93.02, 93.07] nm that misses the reference and is rejected by the absolute chi-square gate (chi2/nu 1.98). On the real micrograph only the colour temperature and the vignetting are determined; thickness, index, dispersion, gain and gamma are combinations - a point versus grooves in the chi-square landscape. Fixing the index gives d = 86.65 nm within 0.2 nm but 12 % low, and the absolute gate (chi2/nu 11.7) and the residual PCA (two components) reject it: the camera’s spectral sensitivities are missing from the model. Synthetic: 40 of 40 seeds within 3 CRLB; the residual PCA finds a dispersion-free model at 500 nm and four angles. Honest limit: a constant index with free A is not rejectable on a 98 nm film (B has only an upper limit). Four probes flip the gates. Nothing learned, 17 gates.
↑ Calibrating a Thin-Film Model to Real Images — and Giving No Number to What the Data Do Not Determine ―― Fit the coefficients of a thin-film forward model to real images, and give no number to a coefficient the data do not determine (sim-to-real calibration with the simcal ops, planned and screened with multivar). Model: transfer matrix (filmoptics) x black-body illuminant (spectralrad) x CIE 1931 to sRGB x gain, gamma and vignetting. Planning: seven knobs on an L18 array; Morris screening puts illumination x gain into one combination (sensitivity ratio 1.0000) and a 500 nm film seen at one wavelength fixes only A^1 d^0.93 (optical path n d); for the 98 nm film the interface reflectance dominates and the exponent departs from n d, stated. Core, public measured data (Schwarz et al. 2024, CC BY 4.0): fitting thickness, Cauchy A and B, NA and gain to the 100 % coverage microspectroscopy gives no point estimate (local collinearity gamma > 10) but a 95 % profile-likelihood interval of [94.6, 99.0] nm (width 4.4 nm) that contains the paper’s 98.5 nm reference; the noise comes from a separate measurement (bare silicon) and all six over-fitting gates pass on held-out wavelengths. The normal-incidence model with the table index gives a narrow [93.02, 93.07] nm that misses the reference and is rejected by the absolute chi-square gate (chi2/nu 1.98). On the real micrograph only the colour temperature and the vignetting are determined; thickness, index, dispersion, gain and gamma are combinations - a point versus grooves in the chi-square landscape. Fixing the index gives d = 86.65 nm within 0.2 nm but 12 % low, and the absolute gate (chi2/nu 11.7) and the residual PCA (two components) reject it: the camera’s spectral sensitivities are missing from the model. Synthetic: 40 of 40 seeds within 3 CRLB; the residual PCA finds a dispersion-free model at 500 nm and four angles. Honest limit: a constant index with free A is not rejectable on a 98 nm film (B has only an upper limit). Four probes flip the gates. Nothing learned, 17 gates.
↑ The measurement ―― Si 上の SiO₂(基準 98.5 nm)の実測の反射率に、5 本のつまみの模型(実線)と直入射・材料表の n の模型(破線)を合わせた。どちらも見た目はよく重なる —— 違いは残差を雑音の床 σ = 0.00054(裸の Si の別の測定から)で割ると見える(次の図)。 (figure labels are in Japanese; the numbers are the same)
↑ 豊かな模型の χ²/ν は 0.29、直入射・材料表の n は 1.98(絶対の χ² の門で p < 1e-3 → 拒否)。
↑ 膜厚を固定して A・B・NA・利得を合わせ直したときの χ² の増え方(プロファイル尤度)。
↑ 実測と同じ波長・同じ雑音 σ で、膜厚 98.5 nm・利得 1 を作って合わせた。
↑ 材料表の n を固定し、膜厚・色温度・利得・ガンマ・周辺減光を区画(40 px 角)の平均に合わせた: d = 86.6 nm(基準 98.5 nm と -12 %)、T = 6224 K、γ = 2.51。
py -3.11 examples/poc_simcal_thin_film.py
Source: examples/poc_simcal_thin_film.py
This run produced 9 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · blackbody_spectrum · color_bar · doe_decode · doe_orthogonal_array · gamma · gaussian · multilayer_reflectance · mv_confounding · mv_pca · nice_ticks · plot_series · simcal_discrepancy · simcal_fit · simcal_identifiability · simcal_model · simcal_observe · simcal_report · simcal_screen · simcal_simulate · simcal_uncertainty · simcal_validate · spectrum_to_srgb … (+2)
Estimating the illuminant to restore colour, peeling the layers of a painting with multiple wavelengths, separating specular reflection with polarisation. The 13 exhibits here synthesise linear radiance from known spectral reflectances, known illuminants and the Fresnel equations, then compare the separation against that truth.
The conclusion in every case was that the failure axes are orthogonal. Max-RGB is best when a white patch is present and gets 8x worse when the single brightest patch is removed; grey-world shrugs off saturation but loses once chromatic content exceeds 20 %; under a reference illuminant every method loses to doing nothing; adding bands does not win, adding near-infrared does.
The shared warning is to convert to linear radiance before calling anything. Pass sRGB-gamma values and no exception is raised; the separation simply degrades in silence. Failure without an exception is the most common pattern in the whole museum.
A spectral cube of ground, underdrawing, glaze and fading stacked in layers, then unmixed. Splitting the visible range into 16 bands does no better than RGB (recall 0.118 versus 0.119); what won was adding near-infrared. The same detector scores an AUC of 1.000 on ultramarine, 0.630 on azurite and 0.013 on losses — averaged, all of that vanishes. Restoring the unfaded colour barely beat the null, ΔE00 16.79 → 16.46.
↑ Peeling Layers With Many Wavelengths — Underdrawing, Ground, Glaze and Fading, Truth in Hand ―― A spectral cube of ground, underdrawing, glaze and fading stacked in layers, then unmixed. Splitting the visible range into 16 bands does no better than RGB (recall 0.118 versus 0.119); what won was adding near-infrared. The same detector scores an AUC of 1.000 on ultramarine, 0.630 on azurite and 0.013 on losses — averaged, all of that vanishes. Restoring the unfaded colour barely beat the null, ΔE00 16.79 → 16.46.
↑ The measurement ―― 近赤外の差分は剥落部(楕円)で消え、近赤外 1 枚は面ごとに水準が違う。塗り分けは 1–99 分位でクリップした表示のみ。 (figure labels are in Japanese; the numbers are the same)
↑ 半分を割るのは tau 0.30–0.60 のあいだ。
↑ ゼロ点の線より下に来た復元が 1 本も無い。
py -3.11 examples/poc_pigment_unmixing.py
Source: examples/poc_pigment_unmixing.py
This run produced 4 figures in total - see them all
Ops used (notes): delta_e_map · linear_to_srgb · spectrum_to_srgb · threshold
Colour deconvolution of a real immunostain (haematoxylin plus DAB). On synthetic data it separates perfectly — recompose one stain at unit concentration, solve again, and recovery is 1.0000 with 0.0000 crosstalk — so anyone looking only at synthesis concludes the problem is solved. On the real slide the haematoxylin concentration runs down to -4.446 and 11.51 % of pixels are negative, which is physically impossible: a negative concentration means the dye emitted light. Rotating the stain vectors within their plane by plus or minus twenty degrees moves the haematoxylin median from 0.0471 to 0.1348, a factor of 2.86, and DAB from 0.3590 to 0.1836 — while the residual channel, the quantity everyone reads as goodness of fit, stays at 0.0345 with a total spread of 3.3e-16. The geometry says why: the plane the two stains span is unchanged by a rotation inside it, so the component orthogonal to that plane cannot move. The negative fraction of the rotated stain is equally blind, fixed at 11.51 %, because its dual vector changes length but not direction. Only the partner moves, from 0.00 % at minus twenty degrees to 30.38 % at plus twenty: the watchdog has to watch the other stain, not itself, and being monotone it bounds the error from one side only. The round trip through recompose closes to 1e-06, which refutes none of this.
↑ Separating a Real Immunostain by Colour — The Watchdog Was Blind to Exactly the Error It Should Catch ―― Colour deconvolution of a real immunostain (haematoxylin plus DAB). On synthetic data it separates perfectly — recompose one stain at unit concentration, solve again, and recovery is 1.0000 with 0.0000 crosstalk — so anyone looking only at synthesis concludes the problem is solved. On the real slide the haematoxylin concentration runs down to -4.446 and 11.51 % of pixels are negative, which is physically impossible: a negative concentration means the dye emitted light. Rotating the stain vectors within their plane by plus or minus twenty degrees moves the haematoxylin median from 0.0471 to 0.1348, a factor of 2.86, and DAB from 0.3590 to 0.1836 — while the residual channel, the quantity everyone reads as goodness of fit, stays at 0.0345 with a total spread of 3.3e-16. The geometry says why: the plane the two stains span is unchanged by a rotation inside it, so the component orthogonal to that plane cannot move. The negative fraction of the rotated stain is equally blind, fixed at 11.51 %, because its dual vector changes length but not direction. Only the partner moves, from 0.00 % at minus twenty degrees to 30.38 % at plus twenty: the watchdog has to watch the other stain, not itself, and being monotone it bounds the error from one side only. The round trip through recompose closes to 1e-06, which refutes none of this.
↑ The measurement ―― H の中央値は 0.0656 -> 0.1348。残差の絶対中央値は 0.0345 のまま。 (figure labels are in Japanese; the numbers are the same)
↑ 残差と自分の負率は平坦。
↑ 濃度は 2.9 倍動くのに残差は 4 桁目まで同じ。
py -3.11 examples/poc_real_stain_unmix.py
Source: examples/poc_real_stain_unmix.py
This run produced 4 figures in total - see them all
Linear RGB synthesised from 24 known spectral reflectances and known illuminants, with illuminant estimation scored by recovery angular error. Max-RGB is best at a median of 1.06 degrees over 11 illuminants, but removing the single brightest patch takes it to 8.45 degrees, and tripling exposure to saturate 43 % of pixels takes it to 13.61 degrees, identical to doing nothing. Even a diagonal correction with the true illuminant leaves a mean ΔE00 of 5.07 at 2500 K.
↑ Colour Constancy (White Balance) — No Method Works, Only Conditions Do ―― Linear RGB synthesised from 24 known spectral reflectances and known illuminants, with illuminant estimation scored by recovery angular error. Max-RGB is best at a median of 1.06 degrees over 11 illuminants, but removing the single brightest patch takes it to 8.45 degrees, and tripling exposure to saturate 43 % of pixels takes it to 13.61 degrees, identical to doing nothing. Even a diagonal correction with the true illuminant leaves a mean ΔE00 of 5.07 at 2500 K.
↑ The measurement ―― 灰色世界はゼロ点(何もしない)の線を 0.1〜0.2 の間で上抜けする = そこから先は回すだけ損。白パッチ法には崖が無い。 (figure labels are in Japanese; the numbers are the same)
↑ 露出 3 以上で白パッチ法の線が「何もしない」に重なる(max が (1,1,1) に張り付く)。
↑ 最右列が推定誤差の実費。
py -3.11 examples/poc_white_balance.py
Source: examples/poc_white_balance.py
This run produced 4 figures in total - see them all
Ops used (notes): delta_e_map · gauss_filter · illuminant_from_dichromatic_planes · laplace · linear_to_srgb · mean_image · prewitt_amp · roberts · sobel_amp · spectrum_to_srgb
So that the observing optics (lenses, windows, mirrors, films) and the observed object (wafer, mould, solder) can read the same material table, 86 glasses, crystals, semiconductors, metals, liquids and plastics are put into one record type. Sixteen new ops (materials, category matprop of opsoptics): material_table / material_record, material_optical_constants (n, k, α, ε and normal reflectance at one wavelength, with the source), material_dispersion / material_extinction / material_absorption / material_transmittance / material_reflectance (all as pairs (λ nm, value), the same shape as source and QE curves), abbe_number / glass_map, thermo_optic_coefficient (catalogue formula A), plate_focus_shift (focus shift and longitudinal spherical aberration of a plane-parallel plate), silicon_free_carrier_absorption (Schroder 1978), material_from_sellmeier / material_from_curves and read_glass_catalog_agf (the user’s own .agf, all 13 dispersion formulas of that format). The numbers are the primary literature of the refractiveindex.info database (CC0) only; vendor catalogues are not bundled. The n_d / V_d of all 46 glasses agree with the catalogue values to 4e-6 / 0.005, and the second implementation optiland 0.6.3 agrees at 23 points to 5e-10. Silicon (Green 2008) reproduces the three-decade drop k(1.0 µm) 5.09e-4 to k(1.2 µm) 2.1e-7, so a 775 µm wafer transmits 0.3 % at 1.0 µm and 52 % at 1.2 µm (closed form to 1e-9). Silver reflects 0.982 at 500 nm, aluminium dips to 0.866 at 820 nm, gold rises from 0.475 at 500 nm to 0.971 at 700 nm, and the colours come from the colour-matching functions, not from paint. Polycarbonate disperses 2.3 times more than BK7-class glass, which has dn/dT 1.62e-6 /K at the e line. Step 2 makes the lens (raytrace), mirror (glassmirror) and scene (optscene.scene_material) ops read the same table: lenses of other makers’ glasses and plastics match the thick-lens closed form at the F, d and C lines to 1e-14 mm, and axial colour follows 1/V_d within 3 % for five materials; the 20 Sellmeier sets raytrace had copied by hand are the same curves to 4e-16. Finding: the first table used REELS-derived data for W and Pt, with 550 nm reflectances of 0.84 / 0.93 against 0.50 / 0.64-0.73 from other measurements; W now uses Weaver, Pt Tselin 2024, and a gate checks against an independent dataset. Nothing learned, nothing calibrated, 46 gates.
↑ A Material Optical-Property Library — 86 Glasses, Crystals, Semiconductors, Metals, Liquids and Plastics in One Table, Scored on Catalogue Values and a Second Implementation ―― So that the observing optics (lenses, windows, mirrors, films) and the observed object (wafer, mould, solder) can read the same material table, 86 glasses, crystals, semiconductors, metals, liquids and plastics are put into one record type. Sixteen new ops (materials, category matprop of opsoptics): material_table / material_record, material_optical_constants (n, k, α, ε and normal reflectance at one wavelength, with the source), material_dispersion / material_extinction / material_absorption / material_transmittance / material_reflectance (all as pairs (λ nm, value), the same shape as source and QE curves), abbe_number / glass_map, thermo_optic_coefficient (catalogue formula A), plate_focus_shift (focus shift and longitudinal spherical aberration of a plane-parallel plate), silicon_free_carrier_absorption (Schroder 1978), material_from_sellmeier / material_from_curves and read_glass_catalog_agf (the user’s own .agf, all 13 dispersion formulas of that format). The numbers are the primary literature of the refractiveindex.info database (CC0) only; vendor catalogues are not bundled. The n_d / V_d of all 46 glasses agree with the catalogue values to 4e-6 / 0.005, and the second implementation optiland 0.6.3 agrees at 23 points to 5e-10. Silicon (Green 2008) reproduces the three-decade drop k(1.0 µm) 5.09e-4 to k(1.2 µm) 2.1e-7, so a 775 µm wafer transmits 0.3 % at 1.0 µm and 52 % at 1.2 µm (closed form to 1e-9). Silver reflects 0.982 at 500 nm, aluminium dips to 0.866 at 820 nm, gold rises from 0.475 at 500 nm to 0.971 at 700 nm, and the colours come from the colour-matching functions, not from paint. Polycarbonate disperses 2.3 times more than BK7-class glass, which has dn/dT 1.62e-6 /K at the e line. Step 2 makes the lens (raytrace), mirror (glassmirror) and scene (optscene.scene_material) ops read the same table: lenses of other makers’ glasses and plastics match the thick-lens closed form at the F, d and C lines to 1e-14 mm, and axial colour follows 1/V_d within 3 % for five materials; the 20 Sellmeier sets raytrace had copied by hand are the same curves to 4e-16. Finding: the first table used REELS-derived data for W and Pt, with 550 nm reflectances of 0.84 / 0.93 against 0.50 / 0.64-0.73 from other measurements; W now uses Weaver, Pt Tselin 2024, and a gate checks against an independent dataset. Nothing learned, nothing calibrated, 46 gates.
↑ The measurement ―― 4 つの公開カタログ(記号 A〜D)の Sellmeier 式とべき級数の分散式を同じレコード型で評価した。硝材コードは n_d − 1 の小数 3 桁と V_d × 10(係数の出どころは公開の硝材カタログ値)。 (figure labels are in Japanese; the numbers are the same)
↑ 同じ一次文献を optiland(MIT)が別のコードで評価した値。
↑ 吸収端の上は厚さに関わらず 0、下は (1−R)²/(1−R²) ≈ 0.53 に漸近(n ≈ 3.5 の Fresnel 損失)。
↑ n,k → 分光反射率 → CIE 等色関数 → sRGB。
↑ R = ±50 mm・厚さ 4 mm の両凸レンズ。
↑ The animation ―― 厚さ 0.05 → 3 mm: 吸収端が長波長側へ押され、1.1 µm の肩が消えていく。薄いダイなら 1.0 µm 台でも透け、厚いウェハは 1.2 µm 以上。
py -3.11 examples/poc_material_optical_library.py
Source: examples/poc_material_optical_library.py
This run produced 17 figures in total - see them all
Ops used (notes): abbe_number · acoustic_impedance_mismatch · chromatic_shift · fresnel_conductor · glass · glass_catalog · glass_map · lens_system · material_absorption · material_acoustic · material_dispersion · material_extinction · material_from_curves · material_from_sellmeier · material_optical_constants · material_record · material_reflectance · material_table · material_transmittance · metal_mirror_rgb · metal_optical_constants · paraxial_trace · plate_focus_shift · read_glass_catalog_agf … (+7)
The back side of a silicon die is inspected with near-infrared light, where silicon is transparent; but those wavelengths are also where a silicon sensor stops responding, so the same absorption edge is a window for the object and a wall for the sensor. One Si record of the material table (Green 2008) is used for both the wafer transmittance and the photodiode QE. Nine new ops (spectralrad, category spectral of opsoptics), all returning pairs (λ nm, value) like the material curves: blackbody, CIE illuminant A, LED, band-pass, Si photodiode QE, energy-to-photon conversion, curve product, integral and summary. CIE A matches its table to 1.6e-6, the blackbody obeys Wien and Stefan–Boltzmann, and the QE matches its closed form to 4e-19. The best LED peak moves to longer wavelengths as the wafer thickens (50 µm: 935 nm, 775 µm: 1065 nm). The usual 1050 nm LED is within 5 % of the best for a thick wafer but yields 3.4 times fewer electrons through a 50 µm die; 1200 nm, where the wafer is most transparent, gives 28 times fewer with a silicon sensor. A deeper collecting sensor (3 to 50 µm) gives 16 times more signal without moving the optimum, which the wafer sets. A CNR of 20 through 775 µm needs 1.2e6 photons per open pixel, and the CNR measured in the images matches e/√(e + read²) within 10 %. Lowering the focus mechanically by the wafer thickness misses by t(1 − 1/n) = 557 µm (W20 = 2.6 waves, diffraction PSF) and widens edges eightfold. Chapter 6 (2026-10-15) puts the same wafer and back-side wiring into a 3-D scene and images it with rays through optscene’s wavelength integral (the LED spectrum split into 15 wavelengths, silicon n and k from the material table, a telecentric lens, the silicon sensor’s QE). The open pixels match an independently written integral ∫LED·λ·(1 − R)²e^{−αt}·QE to 0.3 % (2.8 % at 1200 nm), the wafer is opaque at 850 nm (5e-9), and the wiring shadows fall on exactly the pixels the geometry predicts. Nothing learned, nothing calibrated, 26 gates.
↑ Near-Infrared Inspection Through a Silicon Wafer — Choosing the LED Wavelength from the Source × Wafer × Sensor Integral ―― The back side of a silicon die is inspected with near-infrared light, where silicon is transparent; but those wavelengths are also where a silicon sensor stops responding, so the same absorption edge is a window for the object and a wall for the sensor. One Si record of the material table (Green 2008) is used for both the wafer transmittance and the photodiode QE. Nine new ops (spectralrad, category spectral of opsoptics), all returning pairs (λ nm, value) like the material curves: blackbody, CIE illuminant A, LED, band-pass, Si photodiode QE, energy-to-photon conversion, curve product, integral and summary. CIE A matches its table to 1.6e-6, the blackbody obeys Wien and Stefan–Boltzmann, and the QE matches its closed form to 4e-19. The best LED peak moves to longer wavelengths as the wafer thickens (50 µm: 935 nm, 775 µm: 1065 nm). The usual 1050 nm LED is within 5 % of the best for a thick wafer but yields 3.4 times fewer electrons through a 50 µm die; 1200 nm, where the wafer is most transparent, gives 28 times fewer with a silicon sensor. A deeper collecting sensor (3 to 50 µm) gives 16 times more signal without moving the optimum, which the wafer sets. A CNR of 20 through 775 µm needs 1.2e6 photons per open pixel, and the CNR measured in the images matches e/√(e + read²) within 10 %. Lowering the focus mechanically by the wafer thickness misses by t(1 − 1/n) = 557 µm (W20 = 2.6 waves, diffraction PSF) and widens edges eightfold. Chapter 6 (2026-10-15) puts the same wafer and back-side wiring into a 3-D scene and images it with rays through optscene’s wavelength integral (the LED spectrum split into 15 wavelengths, silicon n and k from the material table, a telecentric lens, the silicon sensor’s QE). The open pixels match an independently written integral ∫LED·λ·(1 − R)²e^{−αt}·QE to 0.3 % (2.8 % at 1200 nm), the wafer is opaque at 850 nm (5e-9), and the wiring shadows fall on exactly the pixels the geometry predicts. Nothing learned, nothing calibrated, 26 gates.
↑ The measurement ―― 最良のピーク: 50 µm → 935 nm、150 µm → 1000 nm、300 µm → 1030 nm、775 µm → 1065 nm。薄いダイは吸収端の手前の 1.0 µm 台でも透け、センサが感じる短い側が勝つ。厚いウェハは吸収端の肩まで押し出される。 (figure labels are in Japanese; the numbers are the same)
↑ 775 µm の Si ウェハの裏の配線を、同じ光子数(開口 1 画素に 1.2e+06)の 3 つの LED で撮った。
↑ 左: ウェハの厚さだけ機械的に下げた焦点(像は 557 µm ずれ、W20 = 2.6 波長、回折の PSF で計算)。
↑ 同じ 775 µm のウェハと裏の配線を 3-D のシーンに置き、光線で撮った(optscene の波長積分、テレセンのレンズ、Si センサ、第 4 章と同じ光子数)。
↑ The animation ―― ウェハを 30 µm から 1.5 mm まで厚くすると、最良の LED が長波長へ滑っていく。
py -3.11 examples/poc_nir_silicon_transmission.py
Source: examples/poc_nir_silicon_transmission.py
This run produced 6 figures in total - see them all
Ops used (notes): bandpass_spectrum · blackbody_spectrum · camera_rays · cie_illuminant_a · defocus_from_shift · energy_to_photons · gaussian · layout_capture · led_spectrum · lens_spec · light_spec · material_optical_constants · material_transmittance · plate_focus_shift · pupil_blur · render_optscene · scene_box · scene_material · sensor_spec · silicon_photodiode_qe · spectral_integral · spectral_integral_table · spectral_product · spectral_stats … (+2)
Inspection light travels in light guides (fibre bundles) and interferometric or spectroscopic light in single-mode fibre; both are set by the dispersion of the glass (the material table) and the guiding equations. Seven new ops (fiberoptics, category fiber of opsoptics): group index, material dispersion, numerical aperture, V number, LP_lm modes from the weakly guiding characteristic equation, the LP01 mode field diameter and light-guide efficiency. Fused silica has group index 1.4626 at 1.55 µm, its material-dispersion zero at 1272.8 nm and D = 21.9 ps/(nm km) at 1.55 µm, all within the published ranges. The mode count switches from 2 to 6 within 0.001 of the Bessel zero 2.4048, reaches 456 at V = 30 against V²/2 = 450, and the LP01 b(V) agrees with the Rudolph-Neumann fit to 0.24 %. LP02 is bound only exponentially weakly just above its cutoff and becomes resolvable in floating point about 0.05 above it, a limit now written into the operator. The mode field diameter agrees with Marcuse’s 1977 fit to 0.8 %, while the second-moment definition is 11 % larger at V = 1.2, so an MFD means nothing without its definition. A core radius of 4.1 µm and NA 0.12 give a cutoff of 1285 nm and an MFD of 9.06 µm at 1310 nm, inside the ITU-T G.652 range of 8.6 to 9.5 µm. A Lambertian LED fills a bundle of smaller NA as (NA_bundle/NA_source)², and the chain LED, 2 m light guide, 775 µm silicon wafer and silicon sensor yields 2.2e-4 electrons per photon. Nothing learned, nothing calibrated, 17 gates.
↑ Optical Fibres and Light Guides — From the Material Table’s Fused Silica to Modes, Dispersion, Mode Field and Illumination Efficiency ―― Inspection light travels in light guides (fibre bundles) and interferometric or spectroscopic light in single-mode fibre; both are set by the dispersion of the glass (the material table) and the guiding equations. Seven new ops (fiberoptics, category fiber of opsoptics): group index, material dispersion, numerical aperture, V number, LP_lm modes from the weakly guiding characteristic equation, the LP01 mode field diameter and light-guide efficiency. Fused silica has group index 1.4626 at 1.55 µm, its material-dispersion zero at 1272.8 nm and D = 21.9 ps/(nm km) at 1.55 µm, all within the published ranges. The mode count switches from 2 to 6 within 0.001 of the Bessel zero 2.4048, reaches 456 at V = 30 against V²/2 = 450, and the LP01 b(V) agrees with the Rudolph-Neumann fit to 0.24 %. LP02 is bound only exponentially weakly just above its cutoff and becomes resolvable in floating point about 0.05 above it, a limit now written into the operator. The mode field diameter agrees with Marcuse’s 1977 fit to 0.8 %, while the second-moment definition is 11 % larger at V = 1.2, so an MFD means nothing without its definition. A core radius of 4.1 µm and NA 0.12 give a cutoff of 1285 nm and an MFD of 9.06 µm at 1310 nm, inside the ITU-T G.652 range of 8.6 to 9.5 µm. A Lambertian LED fills a bundle of smaller NA as (NA_bundle/NA_source)², and the chain LED, 2 m light guide, 775 µm silicon wafer and silicon sensor yields 2.2e-4 electrons per photon. Nothing learned, nothing calibrated, 17 gates.
↑ The measurement ―― パルスの包絡は c/n_g で進む。材料表の溶融石英(Malitson 1965)から差分で求めた。 (figure labels are in Japanese; the numbers are the same)
↑ 零点より短い波長は正常分散(青が遅い)、長い波長は異常分散。
↑ V が小さいほど場がクラッドへ染み出し、指数関数の裾が 2 次モーメントを押し上げる。
↑ コアの中は J0、外は K0。
↑ 光源より NA の小さい束は、立体角の比 (NA_束/NA_光源)² しか受け取らない。
py -3.11 examples/poc_fiber_light_guide.py
Source: examples/poc_fiber_light_guide.py
This run produced 6 figures in total - see them all
Ops used (notes): energy_to_photons · fiber_bundle_coupling · fiber_lp_modes · fiber_mode_field_diameter · fiber_numerical_aperture · fiber_v_number · led_spectrum · material_dispersion · material_group_index · material_gvd · material_transmittance · silicon_photodiode_qe · spectral_integral · spectral_product
The plain-text prescription (.zmx) that commercial sequential ray-tracing programs save becomes a raytrace prescription, goes through real rays, Seidel sums, PSF (point spread function), MTF (modulation transfer function) and optimisation, and is written back in the same format. Four new ops (lensfile, category lensfile of opsoptics): read, pick a field / wavelength, write, and a keyword inventory. Every keyword is classified as read, ignored (with a reason) or unsupported; unsupported surfaces (coordinate breaks, ideal lenses, toroids …), an r² aspheric term and glass names that exist in the material table only as an alias are refused, and approximations are imported only with allow_partial and are marked. Against what ray-optics (BSD-3) and optiland (MIT) computed from the very same files with their own readers, first-order data agree to 6e-16 on four designs, the Seidel sums to 6e-8 and 14 real rays to 3e-8 mm on the image. Swapping the middle element of the triplet to a 648/338 flint blows the RMS spot from 13.9 to 332 µm; lensopt on the six curvatures brings it back to 14.1 µm at the original 50.02 mm focal length, and the written .zmx reads back identically. Building this exposed and fixed a defect in raytrace: the conic intersection took the root with the smallest |
t | , so a concave surface after a long air gap turned every ray into a ‘vignetted’ one. No learning, no calibration, 21 gates. |
*↑ Reading a Lens Design File (.zmx), Analysing It, Fixing It and Writing It Back — Scored Against Two Other Readers and a Round Trip ―― The plain-text prescription (.zmx) that commercial sequential ray-tracing programs save becomes a raytrace prescription, goes through real rays, Seidel sums, PSF (point spread function), MTF (modulation transfer function) and optimisation, and is written back in the same format. Four new ops (lensfile, category lensfile of opsoptics): read, pick a field / wavelength, write, and a keyword inventory. Every keyword is classified as read, ignored (with a reason) or unsupported; unsupported surfaces (coordinate breaks, ideal lenses, toroids …), an r² aspheric term and glass names that exist in the material table only as an alias are refused, and approximations are imported only with allow_partial and are marked. Against what ray-optics (BSD-3) and optiland (MIT) computed from the very same files with their own readers, first-order data agree to 6e-16 on four designs, the Seidel sums to 6e-8 and 14 real rays to 3e-8 mm on the image. Swapping the middle element of the triplet to a 648/338 flint blows the RMS spot from 13.9 to 332 µm; lensopt on the six curvatures brings it back to 14.1 µm at the original 50.02 mm focal length, and the written .zmx reads back identically. Building this exposed and fixed a defect in raytrace: the conic intersection took the root with the smallest |
t | , so a concave surface after a long air gap turned every ray into a ‘vignetted’ one. No learning, no calibration, 21 gates.* |
Terms: PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.; MTF — The magnitude of the optical transfer function, showing how image contrast is transferred at each spatial frequency.
↑ The measurement ―― 読み込んだ処方の子午断面図(optdraw)。視野 0・14・20° の実光線 5 本ずつ、寸法は EFL・BFL・全長・像高。 (figure labels are in Japanese; the numbers are the same)
↑ S_I 球面、S_II コマ、S_III 非点、S_IV ペッツバール、S_V 歪曲。
↑ 実収差の瞳(OPD)から PSF を FFT で作り、その FFT が MTF。
↑ 中央の負レンズを重いフリント 648/338 に差し替えて崩れた三枚玉を、6 面の曲率を変数に EFL を元の値に固定して直す。
↑ 記録値(tests/data/lensfile/second_recorded.json)。
py -3.11 examples/poc_lens_file_roundtrip.py
Source: examples/poc_lens_file_roundtrip.py
This run produced 6 figures in total - see them all
Ops used (notes): drawing_raster · lens_file_read · lens_file_report · lens_file_system · lens_file_write · lens_section_drawing · lens_system · merit_function · mtf50 · mtf_diffraction · noncoaxial_example · noncoaxial_layout · noncoaxial_system · optimize_lens · paraxial_trace · psf_to_mtf · ray_bundle · seidel_coefficients · spot_diagram · trace_rays · with_wavelength
Several configurations that differ only in a spacing or a curvature, such as wide, standard and tele, are held as differences of one prescription instead of copies. Five new ops (lensconfig, category lensconfig of opsoptics): resolve all configurations, resolve one, the operand-by-configuration table, one metric (EFL, back focal length, RMS spot, MTF, encircled energy) per configuration, and append a configuration. lens_file_read / lens_file_write read and write the MNUM table of the design file (.zmx) with its THIC / CRVT / GLSS / PRAM rows; the row layout was checked on 24 public zoom-lens files. For two thin lenses (f1 = 100, f2 = 60 mm) at separations 10 / 20 / 30 mm the EFL and back focal length match the closed form f1 f2 / (f1 + f2 - d) to 1e-9, and for one ideal lens whose f changes per configuration the encircled energy is the Airy 83.8 % (worst difference 1e-4). A Cooke triplet with the gap between the first two lenses changed (the image distance refocused per configuration) has EFL 50.26 / 50.02 / 49.79 mm and on-axis RMS spot 3.6 / 12.0 / 26.2 um. Written to a .zmx with an MNUM table and read back, every overridden value and the EFL agree to 1e-12 per configuration and write-read-write gives the same table. A system whose aperture, field or wavelength differs between configurations cannot be carried by the verified rows, so the writer refuses it instead of dropping the difference. Learning 0, calibration 0, 14 gates.
↑ A Zoom Lens as Differences of One Prescription — Scored Against the Two-Thin-Lens Closed Form and a Round Trip Through the Design File’s Multi-Configuration Table ―― Several configurations that differ only in a spacing or a curvature, such as wide, standard and tele, are held as differences of one prescription instead of copies. Five new ops (lensconfig, category lensconfig of opsoptics): resolve all configurations, resolve one, the operand-by-configuration table, one metric (EFL, back focal length, RMS spot, MTF, encircled energy) per configuration, and append a configuration. lens_file_read / lens_file_write read and write the MNUM table of the design file (.zmx) with its THIC / CRVT / GLSS / PRAM rows; the row layout was checked on 24 public zoom-lens files. For two thin lenses (f1 = 100, f2 = 60 mm) at separations 10 / 20 / 30 mm the EFL and back focal length match the closed form f1 f2 / (f1 + f2 - d) to 1e-9, and for one ideal lens whose f changes per configuration the encircled energy is the Airy 83.8 % (worst difference 1e-4). A Cooke triplet with the gap between the first two lenses changed (the image distance refocused per configuration) has EFL 50.26 / 50.02 / 49.79 mm and on-axis RMS spot 3.6 / 12.0 / 26.2 um. Written to a .zmx with an MNUM table and read back, every overridden value and the EFL agree to 1e-12 per configuration and write-read-write gives the same table. A system whose aperture, field or wavelength differs between configurations cannot be carried by the verified rows, so the writer refuses it instead of dropping the difference. Learning 0, calibration 0, 14 gates.
Terms: MTF — The magnitude of the optical transfer function, showing how image contrast is transferred at each spatial frequency.; Airy pattern — The diffraction image of a circular aperture. The Airy-disc radius to the first dark ring is 1.22 λF.
py -3.11 examples/poc_zoom_configurations.py
Source: examples/poc_zoom_configurations.py
This run produced 1 figures in total - see them all
Ops used (notes): encircled_energy · lens_add_configuration · lens_configuration_metrics · lens_configuration_table · lens_configurations · lens_file_read · lens_file_write · lens_system · lens_with_configuration · paraxial_trace
A model glass (n_d, V_d) is swapped for a catalogue glass, and the local optimiser is run from many starting points to collect the local optima. Three new ops (lensglobal, category lensglobal of opsoptics): glass_nearest (the k nearest catalogue glasses in the Abbe plane, distance nd_vd or partial_dispersion with a P_g,F term), lens_substitute_glasses (replace the model glasses of a system by the k-th nearest catalogue glass; beyond the tolerance it is a ValueError and nothing is swapped partially) and lens_global_search (multi-start; the local optimiser is lensopt.optimize_lens itself, the merit is not re-implemented; every local optimum is returned in merit order, coinciding ones are flagged as duplicates, the same seed gives the same result). Each of 8 catalogue glasses is its own nearest neighbour at distance 0, and a model glass made from it is swapped back to its name. The powers of a thin achromat (crown 1.5168 / 64.17, flint 1.6200 / 36.37) from the closed form phi1 = phi V1/(V1 - V2), phi2 = -phi V2/(V1 - V2) are achromatic to 1e-12, and after the swap the F-C power difference becomes phi1’/V1’ + phi2’/V2’ (+5.9e-5 of the power for the nearest pair, -4.5e-3 for k = 2); the traced value agrees with the hand computation to 4e-11 relative. Three external truths for the search. (a) Bending a singlet: the best shape factor at apertures 5 / 3 mm, 0.7374 / 0.7383, extrapolated in ap^2 gives 0.7388, within 1e-3 of the third-order q* = 2(n^2 - 1)/(n + 2) = 0.7397. (b) A thin achromat: all 8 starts reach the closed-form powers to 9.8e-9 relative and collapse into one optimum. (c) A three-curvature achromat: 19 of 20 starts fall into the shallow optimum (merit 1.18e-5) and one reaches the deep one (5.04e-6); a single start stays in the shallow one. Honest note: the local optimiser’s default EFL weight is too stiff for a small-aperture system and stalls at merit 5e-10, so (b) was run with efl_weight = 1. Learning 0, calibration 0, 10 gates.
↑ Glass Substitution and Multi-Start Search — Scored Against the Thin-Achromat Closed Form and the Third-Order Best-Form Bending ―― A model glass (n_d, V_d) is swapped for a catalogue glass, and the local optimiser is run from many starting points to collect the local optima. Three new ops (lensglobal, category lensglobal of opsoptics): glass_nearest (the k nearest catalogue glasses in the Abbe plane, distance nd_vd or partial_dispersion with a P_g,F term), lens_substitute_glasses (replace the model glasses of a system by the k-th nearest catalogue glass; beyond the tolerance it is a ValueError and nothing is swapped partially) and lens_global_search (multi-start; the local optimiser is lensopt.optimize_lens itself, the merit is not re-implemented; every local optimum is returned in merit order, coinciding ones are flagged as duplicates, the same seed gives the same result). Each of 8 catalogue glasses is its own nearest neighbour at distance 0, and a model glass made from it is swapped back to its name. The powers of a thin achromat (crown 1.5168 / 64.17, flint 1.6200 / 36.37) from the closed form phi1 = phi V1/(V1 - V2), phi2 = -phi V2/(V1 - V2) are achromatic to 1e-12, and after the swap the F-C power difference becomes phi1’/V1’ + phi2’/V2’ (+5.9e-5 of the power for the nearest pair, -4.5e-3 for k = 2); the traced value agrees with the hand computation to 4e-11 relative. Three external truths for the search. (a) Bending a singlet: the best shape factor at apertures 5 / 3 mm, 0.7374 / 0.7383, extrapolated in ap^2 gives 0.7388, within 1e-3 of the third-order q = 2(n^2 - 1)/(n + 2) = 0.7397. (b) A thin achromat: all 8 starts reach the closed-form powers to 9.8e-9 relative and collapse into one optimum. (c) A three-curvature achromat: 19 of 20 starts fall into the shallow optimum (merit 1.18e-5) and one reaches the deep one (5.04e-6); a single start stays in the shallow one. Honest note: the local optimiser’s default EFL weight is too stiff for a small-aperture system and stalls at merit 5e-10, so (b) was run with efl_weight = 1. Learning 0, calibration 0, 10 gates.*
py -3.11 examples/poc_lens_global_search.py
Source: examples/poc_lens_global_search.py
This run produced 1 figures in total - see them all
Ops used (notes): glass · glass_map · glass_nearest · lens_global_search · lens_substitute_glasses · lens_system · paraxial_trace · with_wavelength
A designer’s merit function is a table of requirements: “edge thickness at least 1 mm here, EFL 50 mm, a small wavefront”. Keep that table as rows, each with a target, a weight and, for a bound, a one-sided form, and say what pulls the optimiser as a percentage per row. Six new ops (lensmerit, category lensmerit of opsoptics): merit_operands_list (name, meaning, unit and arguments of 32 operands; the 7th slice added ANGM / RSAN for afocal systems) / merit_evaluate (per row value, target, weight, contribution (w (value - target))^2 and share; a bound row contributes 0 while it holds) / merit_default (whole-pupil ray rows plus the EFL hold; spot or wavefront, chief-ray or centroid reference) / merit_boundary_operands (edge thickness of every glass, centre-thickness and air-gap bounds) / optimize_lens_operands (the variables, damped least squares and step sizes of lensopt unchanged, plus an outer discrete loop that picks the glass of g<i> from a candidate list) / merit_contributions (which row dominates, as percentages). The edge thickness is t - sag_i(h_i) + sag_(i+1)(h_(i+1)) at the clear semi-diameters of both surfaces, conic and even aspheric terms included. Scored against closed forms: the thin-achromat power split phi1 = phi V1/(V1 - V2), phi2 = -phi V2/(V1 - V2) (an EFFL + AXCL = 0 optimisation lands on it to 1.7e-14 relative), the plano-convex edge thickness to 1.8e-15, the real ray height and angle of incidence after a refracting sphere against Snell’s law to 1.1e-13, the working F-number of an ideal lens against f/2h to 1.2e-7. The default table (spot, chief-ray reference) equals lensopt.merit_function to 0.0 relative (the gate is 1e-9). Against the recorded numbers of an independent implementation (an MIT-licensed open-source library) the EFL agrees to 1.4e-14, the real ray positions at the image to 1.8e-14 mm and the on-axis OPD to 8.5e-11 waves; the off-axis OPD differs by 0.03 % of the peak at 14 deg and 0.25 % at 20 deg (a convention: the other implementation’s reference sphere has the distance from the image point to the exit-pupil centre as its radius, raytrace.opd_samples the axial distance only). A Cooke-type triplet (the classic 40-degree radii moved by 7-10 %, thin edges, EFL 58 mm) is optimised with a table of wavefront (3 fields x F, d, C) + an EFL hold of 50 mm + bounds (edge thickness >= 1 mm, centre thickness 2-8 mm, air gap >= 0.5 mm): merit 1.03e5 -> 33.9 (1/3041), EFL 57.98 -> 50.0015 mm, edge thickness 1.83 / 2.38 / 0.10 -> 3.37 / 3.78 / 1.00 mm, RMS spot (on axis, 8.4 deg, 12 deg) 25 / 13 / 22 -> 5.8 / 8.4 / 12 um. The dominant row moves from the EFL hold (82 %) to the wavefront row at 12 deg (71 %). From a start whose negative lens has the wrong glass (a crown), with axial- and lateral-colour rows added, the continuous variables alone leave the axial colour at -1.02 mm (glass cannot be fixed by continuous variables). Letting g2 choose among 6 model glasses picks a dense flint: AXCL -1.02 -> -0.059 mm (1/17), LACL at 12 deg -0.051 -> -3.3e-4 mm (1/154), merit 1.26e4 -> 47 (1/265). Honest notes: with bound weight 10 the centre thickness stopped 0.06 mm short, so 100 is used; the optimisation spends its 40 iterations at the active edge bound (1.001 mm); the reduced CI budget (one outer loop) ends at merit 45.6, AXCL 1/18 and LACL 1/167. Learning 0, calibration 0, 25 gates.
↑ Designing a Triplet from a Table of Requirements — A Merit Function with Edge-Thickness, Centre-Thickness and Air-Gap Bounds, Scored Against Closed Forms and an Independent Implementation ―― A designer’s merit function is a table of requirements: “edge thickness at least 1 mm here, EFL 50 mm, a small wavefront”. Keep that table as rows, each with a target, a weight and, for a bound, a one-sided form, and say what pulls the optimiser as a percentage per row. Six new ops (lensmerit, category lensmerit of opsoptics): merit_operands_list (name, meaning, unit and arguments of 32 operands; the 7th slice added ANGM / RSAN for afocal systems) / merit_evaluate (per row value, target, weight, contribution (w (value - target))^2 and share; a bound row contributes 0 while it holds) / merit_default (whole-pupil ray rows plus the EFL hold; spot or wavefront, chief-ray or centroid reference) / merit_boundary_operands (edge thickness of every glass, centre-thickness and air-gap bounds) / optimize_lens_operands (the variables, damped least squares and step sizes of lensopt unchanged, plus an outer discrete loop that picks the glass of g<i> from a candidate list) / merit_contributions (which row dominates, as percentages). The edge thickness is t - sag_i(h_i) + sag_(i+1)(h_(i+1)) at the clear semi-diameters of both surfaces, conic and even aspheric terms included. Scored against closed forms: the thin-achromat power split phi1 = phi V1/(V1 - V2), phi2 = -phi V2/(V1 - V2) (an EFFL + AXCL = 0 optimisation lands on it to 1.7e-14 relative), the plano-convex edge thickness to 1.8e-15, the real ray height and angle of incidence after a refracting sphere against Snell’s law to 1.1e-13, the working F-number of an ideal lens against f/2h to 1.2e-7. The default table (spot, chief-ray reference) equals lensopt.merit_function to 0.0 relative (the gate is 1e-9). Against the recorded numbers of an independent implementation (an MIT-licensed open-source library) the EFL agrees to 1.4e-14, the real ray positions at the image to 1.8e-14 mm and the on-axis OPD to 8.5e-11 waves; the off-axis OPD differs by 0.03 % of the peak at 14 deg and 0.25 % at 20 deg (a convention: the other implementation’s reference sphere has the distance from the image point to the exit-pupil centre as its radius, raytrace.opd_samples the axial distance only). A Cooke-type triplet (the classic 40-degree radii moved by 7-10 %, thin edges, EFL 58 mm) is optimised with a table of wavefront (3 fields x F, d, C) + an EFL hold of 50 mm + bounds (edge thickness >= 1 mm, centre thickness 2-8 mm, air gap >= 0.5 mm): merit 1.03e5 -> 33.9 (1/3041), EFL 57.98 -> 50.0015 mm, edge thickness 1.83 / 2.38 / 0.10 -> 3.37 / 3.78 / 1.00 mm, RMS spot (on axis, 8.4 deg, 12 deg) 25 / 13 / 22 -> 5.8 / 8.4 / 12 um. The dominant row moves from the EFL hold (82 %) to the wavefront row at 12 deg (71 %). From a start whose negative lens has the wrong glass (a crown), with axial- and lateral-colour rows added, the continuous variables alone leave the axial colour at -1.02 mm (glass cannot be fixed by continuous variables). Letting g2 choose among 6 model glasses picks a dense flint: AXCL -1.02 -> -0.059 mm (1/17), LACL at 12 deg -0.051 -> -3.3e-4 mm (1/154), merit 1.26e4 -> 47 (1/265). Honest notes: with bound weight 10 the centre thickness stopped 0.06 mm short, so 100 is used; the optimisation spends its 40 iterations at the active edge bound (1.001 mm); the reduced CI budget (one outer loop) ends at merit 45.6, AXCL 1/18 and LACL 1/167. Learning 0, calibration 0, 25 gates.
↑ The measurement ―― 最適化に伴い12度視野のスポットが収束する。 (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_lens_merit_operands.py
Source: examples/poc_lens_merit_operands.py
This run produced 2 figures in total - see them all
Ops used (notes): lens_system · merit_boundary_operands · merit_contributions · merit_default · merit_evaluate · merit_function · merit_operands_list · optimize_lens_operands · paraxial_trace · ray_bundle · text_box · with_wavelength
A design is a bundle of relations, not a list of numbers. Keep “the image plane is the paraxial focus”, “F/# is 5” and “this surface is concentric with the previous one” as rules on the surface, so that changing the stop radius or a glass lets the rest settle again. Nine new ops (lenssolve, category lenssolve of opsoptics): lens_apply_solves (a copy with every solve resolved) / lens_solve_set / lens_solve_clear / lens_solves_list (rule, solved value, achieved quantity and error) / afocal_system / lens_afocal_report (angular magnification, exit pupil and angular aberration per field in arcminutes) / lens_image_surface / lens_petzval_radius / lens_zmx_solves_roundtrip. A rule sits in solve of the surface dict (R / t next to it hold the solved value) and every raytrace trace resolves it first. raytrace gained an image-surface curvature image_R, afocal, and the aspheric terms a2 (r^2) and odd (r^i, i = 1..8); lensmerit gained the angular magnification ANGM and the angular spot RSAN. Scored against closed forms: the chief-ray height and marginal-ray angle solves equal the hand solution of one linear equation (error 0), the F/# solve has two roots (positive and negative power) and matches both closed-form roots to 3e-17, the aplanatic surface images its object point exactly (spot RMS 3.4e-16 mm; an ordinary R = -3 mm surface 0.51 mm), a concentric ball does not bend a ray aimed at its centre (2.2e-16). A double-Gauss type (an F/# = 5 solve on the front curvature of the last element, a paraxial-image solve on the image distance) re-settles as the stop radius moves from 5.0 to 7.6 mm: the F/# stays 5 (error 8.9e-16) and the image distance follows the paraxial focus (4.4e-16); the last curvature moves R 79 -> 768 mm, the EFL 78.9 -> 119.9 mm, the image distance 47.5 -> 74.6 mm. The frozen-numbers version drifts from F/# 6.34 to 4.17. Honest limit: holding the F/# while the stop opens without redesigning the other surfaces, the RMS spot at 10 deg is 65.8 -> 25.6 um and worse in between (a solve keeps a relation, not the image quality). The F/# solve has two roots (curvature -0.046711 and +0.005637 /mm); the other is a diverging system of EFL -100 mm. Afocal: the ideal-lens Keplerian (f1 = 100, f2 = 25) and Galilean (f2 = -25) telescopes have angular magnification -4 / +4 exactly (EFL = inf does not raise) and angular aberration below 7.5e-13 arcmin. A real telescope of two achromatic doublets (separation 119.9076 mm, angular magnification -4.026, exit pupil of radius 2.48 mm at 30.4 mm) has angular spot RMS 0.60 / 0.71 / 1.05 / 1.63 / 2.55 arcmin at 0 / 0.25 / 0.5 / 0.75 / 1 deg, an on-axis wavefront of 0.041 waves (diffraction limited) and an angular-magnification distortion of 0.85 % at 1 deg. The afocal flag round-trips through a NOTE line of the .zmx (the format’s own afocal line cannot be established from a real file, so none is invented). Curved image surface: for a thin meniscus (concave to the object, f = 99.8 mm) at zero Seidel astigmatism, the image surface of the Petzval radius -151.68 mm (equal to the thin-lens closed form -N f and to the Seidel sum) shrinks the RMS spot at 0.7 field from 1.94 um on the flat plane to 0.226 um (the best-plane floor is 0.222 um); the field-curvature term goes 1.93 -> 0.046 um (1/42). Odd aspheres: the sag matches the hand-written polynomial to 2.2e-16, the intersection satisfies the surface equation to 2.8e-16, and B = 0 is bit-identical to the even asphere. A conical (r^1) plate focuses rays onto a line 102 mm long instead of a point, and the axis crossings match the Snell closed form to 7.1e-14. One term added to a spherical-aberration corrector plate: r^4 takes the on-axis RMS 14.0 -> 0.030 um (1/467), r^3 to 1.97 um (1/7.1), r^1 to 8.3 um, r^2 to 11.5 um. .zmx: three pickups (CURV c 4 source scale offset, PZUP, PPAR) and three rules written as NOTE lines make write -> read -> write a fixed point (difference 1.4e-14); no solve is frozen and dropped. Limits: only the pickups have a form that real files show, so the other kinds (chief / marginal ray, F/#, concentric, aplanatic) round-trip through NOTE lines; odd terms (r^1 above all) are not smooth at the vertex, the r^1 term has no paraxial power, and the Seidel sums, the non-coaxial tracer and PSF / MTF refuse a2 / odd and afocal. Learning 0, calibration 0, 35 gates.
↑ Keeping a Design’s Relations as Solves — A Double-Gauss Type Whose Focus and F-Number Follow the Stop, Afocal Telescopes, a Curved Image Surface and Odd Aspheres ―― A design is a bundle of relations, not a list of numbers. Keep “the image plane is the paraxial focus”, “F/# is 5” and “this surface is concentric with the previous one” as rules on the surface, so that changing the stop radius or a glass lets the rest settle again. Nine new ops (lenssolve, category lenssolve of opsoptics): lens_apply_solves (a copy with every solve resolved) / lens_solve_set / lens_solve_clear / lens_solves_list (rule, solved value, achieved quantity and error) / afocal_system / lens_afocal_report (angular magnification, exit pupil and angular aberration per field in arcminutes) / lens_image_surface / lens_petzval_radius / lens_zmx_solves_roundtrip. A rule sits in solve of the surface dict (R / t next to it hold the solved value) and every raytrace trace resolves it first. raytrace gained an image-surface curvature image_R, afocal, and the aspheric terms a2 (r^2) and odd (r^i, i = 1..8); lensmerit gained the angular magnification ANGM and the angular spot RSAN. Scored against closed forms: the chief-ray height and marginal-ray angle solves equal the hand solution of one linear equation (error 0), the F/# solve has two roots (positive and negative power) and matches both closed-form roots to 3e-17, the aplanatic surface images its object point exactly (spot RMS 3.4e-16 mm; an ordinary R = -3 mm surface 0.51 mm), a concentric ball does not bend a ray aimed at its centre (2.2e-16). A double-Gauss type (an F/# = 5 solve on the front curvature of the last element, a paraxial-image solve on the image distance) re-settles as the stop radius moves from 5.0 to 7.6 mm: the F/# stays 5 (error 8.9e-16) and the image distance follows the paraxial focus (4.4e-16); the last curvature moves R 79 -> 768 mm, the EFL 78.9 -> 119.9 mm, the image distance 47.5 -> 74.6 mm. The frozen-numbers version drifts from F/# 6.34 to 4.17. Honest limit: holding the F/# while the stop opens without redesigning the other surfaces, the RMS spot at 10 deg is 65.8 -> 25.6 um and worse in between (a solve keeps a relation, not the image quality). The F/# solve has two roots (curvature -0.046711 and +0.005637 /mm); the other is a diverging system of EFL -100 mm. Afocal: the ideal-lens Keplerian (f1 = 100, f2 = 25) and Galilean (f2 = -25) telescopes have angular magnification -4 / +4 exactly (EFL = inf does not raise) and angular aberration below 7.5e-13 arcmin. A real telescope of two achromatic doublets (separation 119.9076 mm, angular magnification -4.026, exit pupil of radius 2.48 mm at 30.4 mm) has angular spot RMS 0.60 / 0.71 / 1.05 / 1.63 / 2.55 arcmin at 0 / 0.25 / 0.5 / 0.75 / 1 deg, an on-axis wavefront of 0.041 waves (diffraction limited) and an angular-magnification distortion of 0.85 % at 1 deg. The afocal flag round-trips through a NOTE line of the .zmx (the format’s own afocal line cannot be established from a real file, so none is invented). Curved image surface: for a thin meniscus (concave to the object, f = 99.8 mm) at zero Seidel astigmatism, the image surface of the Petzval radius -151.68 mm (equal to the thin-lens closed form -N f and to the Seidel sum) shrinks the RMS spot at 0.7 field from 1.94 um on the flat plane to 0.226 um (the best-plane floor is 0.222 um); the field-curvature term goes 1.93 -> 0.046 um (1/42). Odd aspheres: the sag matches the hand-written polynomial to 2.2e-16, the intersection satisfies the surface equation to 2.8e-16, and B = 0 is bit-identical to the even asphere. A conical (r^1) plate focuses rays onto a line 102 mm long instead of a point, and the axis crossings match the Snell closed form to 7.1e-14. One term added to a spherical-aberration corrector plate: r^4 takes the on-axis RMS 14.0 -> 0.030 um (1/467), r^3 to 1.97 um (1/7.1), r^1 to 8.3 um, r^2 to 11.5 um. .zmx: three pickups (CURV c 4 source scale offset, PZUP, PPAR) and three rules written as NOTE lines make write -> read -> write a fixed point (difference 1.4e-14); no solve is frozen and dropped. Limits: only the pickups have a form that real files show, so the other kinds (chief / marginal ray, F/#, concentric, aplanatic) round-trip through NOTE lines; odd terms (r^1 above all) are not smooth at the vertex, the r^1 term has no paraxial power, and the Seidel sums, the non-coaxial tracer and PSF / MTF refuse a2 / odd and afocal. Learning 0, calibration 0, 35 gates.
↑ The measurement ―― 解いた曲面像面で視野角を掃引したスポット。 (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_lens_solves_afocal.py
Source: examples/poc_lens_solves_afocal.py
This run produced 2 figures in total - see them all
Ops used (notes): afocal_system · chief_ray · lens_afocal_report · lens_apply_solves · lens_file_read · lens_file_write · lens_image_surface · lens_petzval_radius · lens_solve_clear · lens_solve_set · lens_solves_list · lens_system · lens_zmx_solves_roundtrip · paraxial_trace · ray_bundle · seidel_coefficients · spot_stats · text_box · trace_rays
Foreign matter the same colour as meat or kernels cannot be told apart by a colour camera. Classifying public real images as spectral data detected debris on pork belly with 1.1 % false alarms. With fewer bands the false alarms grew, so a band set that sufficed on synthetic data did not suffice on real data.
↑ Sorting Look-Alike Foreign Matter with Hyperspectral NIR — Two Real Datasets (Plastic Flakes, Foreign Objects on Pork Belly) and a Material-Table Simulation of the Failure Modes ―― Two public hyperspectral datasets (both CC BY 4.0; fetched and cached, not committed) are classified with Fullseye’s ops (read_envi, spec_continuum_removal, spec_angle_mapper, spec_band_selection_fisher) and scored against their own labels. Shredded plastics, six classes (RODARE 3390, a 400–2500 nm spectral camera): continuum removal + SAM reaches a mean class recall of 0.816 (0.439 on raw reflectance — dark flakes lose to additive baselines). A library from a separate scan of reference samples separates PP / PET / PE at 0.878; this PoC’s material-table physics alone gives 0.628 (chance 0.333). Black plastic reflects 0.03 with a C-H trough one sixth as deep. The test header still carried 450 fwhm values for 360 bands and the fail-closed reader refused it; the wavelength labels were verified on the polymer troughs and only fwhm was fixed in a copy. Foreign objects on pork belly, ten classes (Zenodo 17242553, an InGaAs line-scan spectral camera): split by capture, 656k test pixels; SAM gives AUC 0.92 with 10 % false alarms, LDA on all 184 bands detects 0.92 at 1.1 % false alarms, six Fisher bands give 2.1 % and three give 23 % — three bands sufficed in simulation but not on real data. The simulation (material absorption bands, Kubelka–Munk, halogen × fused-silica lens × InGaAs; eight new ops) covers the failure modes: colour-matched foreign matter at RGB AUC 0.48, kernels at 12 % moisture 98 % rejected, a 200 K lamp drift after the white reference 49 %, and carbon black at 1 wt % burying PP vs PE in single-pixel noise. The gap between simulated 1.000 and real 0.82–0.92 is the size of the inverse crime. Parameters were checked against primary sources (USGS spectra, papers, datasheets): PE band strengths cut 2–3x, PET’s two deepest bands added, and the table’s water k switched to Segelstein 1981.
Terms: SAM — A classifier that compares spectra by the angle between their vectors, making it invariant to a common multiplicative scale.
↑ The measurement ―― 実線: 実データの PP / PET / PE の画素の平均(連続体除去)。破線: この PoC の材料表(吸収帯 + Kubelka–Munk)。谷の位置は合い、深さと幅は合わない(模型の強さは推定)。物理モデルだけをライブラリにした 3 種の再現率 0.628、別撮影の基準試料 0.878。出典: E. Arbash ら, Polymers Hyperspectral Imaging, RODARE (HZDR), doi:10.14278/rodare.3390, CC BY 4.0。改変: 試験ヘッダの fwhm を 360 帯に切り詰め、1100〜2400 nm を切り出し、連続体除去。 (figure labels are in Japanese; the numbers are the same)
↑ 出典: E. Arbash ら, Polymers Hyperspectral Imaging, RODARE (HZDR), doi:10.14278/rodare.3390, CC BY 4.0。
↑ 同じトレイ。
↑ 行 = 真値、列 = 判定。
↑ 露光 = 画素の長さ 0.5 mm / 搬送速度。
↑ The animation ―― 0.95〜1.65 µm を 20 nm おきに掃く。1.2 µm 付近で樹脂片が明るく、1.45 µm(水)で殻と木片が暗く沈む —— 1 帯域では分からない異物が、帯域を動かすと順に浮かぶ。
py -3.11 examples/poc_hyperspectral_foreign_matter.py
Source: examples/poc_hyperspectral_foreign_matter.py
This run produced 19 figures in total - see them all
Ops used (notes): carbon_black_absorption · hyperspectral_band_weights · hyperspectral_capture · ingaas_photodiode_qe · kubelka_munk_reflectance · material_nir_absorption · median · nir_model_info · spectral_reflectance_from_material · text_box
Four prescriptions are calculated with a separate reader and ray tracer, comparing EFL, BFL, real rays and RMS spots. At infinity focus with a 1 mm entrance pupil and a 256 by 256 sensor of 5.5 um pixels, illumination, distortion, colour shift, MTF and visibility of a three-pixel scratch are scored. The OPD reference-sphere and pupil-mapping convention difference is retained as a figure. No learning, no calibration, two gates.
↑ Verifying Public .zmx Prescriptions Independently and Scoring Them as Inspection Lenses ―― Four prescriptions are calculated with a separate reader and ray tracer, comparing EFL, BFL, real rays and RMS spots. At infinity focus with a 1 mm entrance pupil and a 256 by 256 sensor of 5.5 um pixels, illumination, distortion, colour shift, MTF and visibility of a three-pixel scratch are scored. The OPD reference-sphere and pupil-mapping convention difference is retained as a figure. No learning, no calibration, two gates.
Terms: MTF — The magnitude of the optical transfer function, showing how image contrast is transferred at each spatial frequency.
↑ The measurement ―― 2 処方、3 視野。各パネルの RMS は同じ実光線から計算。 (figure labels are in Japanese; the numbers are the same)
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_zmx_public_verify/04_inspection_scores.png) |
| *↑ 合格条件は照明≥0.75、 | 歪曲 | ≤2 %、RMS≤1 px、 | 倍率色 | ≤1 px、MTF≥0.2、傷 contrast≥0.1。* |
↑ 出力から測った傷 contrast は -0.424〜-0.397。
↑ 光線座標の門とは別に、軸外 OPD は最大 36.22 % 違う。
↑ The animation ―― 同じ処方のスポットを軸上から最大視野まで掃引する。
py -3.11 examples/poc_zmx_public_verify.py
Source: examples/poc_zmx_public_verify.py
This run produced 9 figures in total - see them all
Ops used (notes): chromatic_shift · distortion_map · lens_file_read · paraxial_trace · psf_from_opd · psf_to_mtf · ray_bundle · render_through_lens · spot_stats
Forgery detection and document rectification. Both tend to be presented through ‘the one image where it was found’ or ‘the one page that came out straight’. The 5 exhibits here fix the paste location and quality, the homography and the lighting themselves, then score with a per-pixel ROC and pixel-level geometric error.
The forgery exhibit is on the detection (defensive) side. Forgeries are generated only because scoring a detector needs ground truth, and the generator is kept to the crudest form possible. What the exhibit shows most strongly is the fact that works against the detector: one press of the save button weakens every cue.
The document exhibit puts a number on a hole: two functions with the same name and different models can be swapped without an exception, returning a plausible picture with the keystone still in it. Shadow removal has no free lunch; the flatter the paper, the more of the faint text disappears.
A document imaged under a known homography and lighting, rectified, and scored by corner and grid error in pixels. The estimate reaches a grid RMS of 1.091 px (doing nothing: 37.376 px), but swapping in the same-named function with a different model (affine) is 32x worse and raises no exception. Shadow strength 0.45 gives a corner RMS of 6.81 px; 0.55 gives 66.06 px — a cliff.
↑ Straightening a Hand-Held Document Photo — Keystone Correction and Shadow Removal Against Ground Truth ―― A document imaged under a known homography and lighting, rectified, and scored by corner and grid error in pixels. The estimate reaches a grid RMS of 1.091 px (doing nothing: 37.376 px), but swapping in the same-named function with a different model (affine) is 32x worse and raises no exception. Shadow strength 0.45 gives a corner RMS of 6.81 px; 0.55 gives 66.06 px — a cliff.
↑ The measurement ―― 平坦・薄字・誤検出なしを同時に満たす行は 1 つも無い。窓 9 が fs.op で届く上限、窓 61 は自前。図の階調は真値で 217 段。 (figure labels are in Japanese; the numbers are the same)
↑ 下寄りの横長の帯が図の階調。
↑ 60 度でも 2 px 台。
py -3.11 examples/poc_document_scan.py
Source: examples/poc_document_scan.py
This run produced 4 figures in total - see them all
Ops used (notes): corner_response · dc_homomorphic · fill_holes · gauss_filter · get_region_contour · gray_tophat · illuminate · illumination_field · image_to_world_plane · mean_image · opening_circle · otsu · project_points · reflect · select_largest · sobel_dir · var_threshold · vector_to_similarity
Ten forgeries made by pasting a JPEG q60 patch onto a q92 background and saving at q95, scored with a per-pixel ROC (true-positive versus false-positive curve). ELA’s single number points the opposite way from the textbook (paste / background = 0.58x), and an untouched image still yields an AUC of 0.797 from position bias alone. The ghost-valley depth reaches an AUC of 0.997, which falls to 0.975 after one whole-image recompression at q75.
↑ Forgery Detection as an ROC (true-positive versus false-positive curve) — Not the One Image Found, but Detection at a Fixed False-Positive Rate ―― Ten forgeries made by pasting a JPEG q60 patch onto a q92 background and saving at q95, scored with a per-pixel ROC (true-positive versus false-positive curve). ELA’s single number points the opposite way from the textbook (paste / background = 0.58x), and an untouched image still yields an AUC of 0.797 from position bias alone. The ghost-valley depth reaches an AUC of 0.997, which falls to 0.975 after one whole-image recompression at q75.
Terms: ROC — A curve of true-positive rate against false-positive rate as the decision threshold varies, showing a binary detector’s trade-off.
↑ The measurement ―― 凡例の数字は AUC。乱数が対角線に乗ることで測り方に偏りが無いと言える。ゴーストA は乱数と重なる。 (figure labels are in Japanese; the numbers are the same)
↑ 凡例の数字は AUC。
↑ 保存ボタン 1 回(q60 再圧縮)で ゴーストV は乱数以下。
py -3.11 examples/poc_forensics_roc.py
Source: examples/poc_forensics_roc.py
This run produced 4 figures in total - see them all
Ops used (notes): copy_move_regions · error_level_map · jpeg_ghost_map · jpeg_ghost_quality · noise_inconsistency_map · reflect
A Voronoi crack network is drawn in closed form for two ground-truth types — drying cracks (small tortuous cells) and age cracks (large grid-like cells) — with paint blotches, varnish gloss, raking light, blur and noise, then measured with a ridge op → skeleton → junction chain. Truth separates the types on all three indicators (cell diameter 18.0 vs 45.2 px, straightness 0.960 vs 1.000, degree-4 fraction 0.20 vs 0.83), yet the age type’s degree-4 fraction drifts toward the drying value under texture (0.87 → 0.64) and raking light (0.70) while diameter and straightness stay put. Both predicted cliffs failed to appear: recall is still 0.696 at 0.15 px width and false positives only 0.382 at texture contrast 0.64. Raking light widens cracks one-sidedly by +0.37 px and shifts the centreline 0.75 px toward the light.
↑ Craquelure networks — of three indicators, only junction degree breaks under imaging conditions ―― A Voronoi crack network is drawn in closed form for two ground-truth types — drying cracks (small tortuous cells) and age cracks (large grid-like cells) — with paint blotches, varnish gloss, raking light, blur and noise, then measured with a ridge op → skeleton → junction chain. Truth separates the types on all three indicators (cell diameter 18.0 vs 45.2 px, straightness 0.960 vs 1.000, degree-4 fraction 0.20 vs 0.83), yet the age type’s degree-4 fraction drifts toward the drying value under texture (0.87 → 0.64) and raking light (0.70) while diameter and straightness stay put. Both predicted cliffs failed to appear: recall is still 0.696 at 0.15 px width and false positives only 0.382 at texture contrast 0.64. Raking light widens cracks one-sidedly by +0.37 px and shifts the centreline 0.75 px toward the light.
↑ The measurement ―― Frangi は分岐点で応答が落ち、斜光でセルが崩れる。 (figure labels are in Japanese; the numbers are the same)
↑ 真値は幾何(ボロノイの頂点・辺)から。
↑ ひびの深さ 0.55。
↑ ぼけが大きいほど小さいセルから消える。
↑ 縁に触れるセルは統計から外す(真値も同じ規約)。
py -3.11 examples/poc_fresco_craquelure.py
Source: examples/poc_fresco_craquelure.py
This run produced 11 figures in total - see them all
Ops used (notes): blob_features · blob_label · blob_overlay · cv_blackhat · distance_transform · hx_split_skeleton_region · hysteresis_threshold · junctions_skeleton · otsu · pruning · r2_endpoints_skeleton · sk_area_opening · sk_frangi · sk_otsu · skeleton · threshold · xsk_meijering · xsk_sato
Two virtual cameras carry a fixed sensitivity pattern K; the fingerprint is estimated from 30 residuals and matched. Under clean conditions the same camera scores a median PCE of 2192 against 15.7 for the other (AUC 1.000), but JPEG-like quantisation at quality 50 cuts PCE to 3.6 % and a 0.5x downscale to 1.5 % — and what killed it was the residual extractor, not the geometry. A camera with K = 0 still yields a ‘fingerprint’ with PCE 1706 if the same background is shot 30 times: the scene turns into a fingerprint.
↑ Camera Fingerprints (PRNU): Which Camera Took This? — The Fingerprint Grows With Frame Count and Dies at the Save Button ―― Two virtual cameras carry a fixed sensitivity pattern K; the fingerprint is estimated from 30 residuals and matched. Under clean conditions the same camera scores a median PCE of 2192 against 15.7 for the other (AUC 1.000), but JPEG-like quantisation at quality 50 cuts PCE to 3.6 % and a 0.5x downscale to 1.5 % — and what killed it was the residual extractor, not the geometry. A camera with K = 0 still yields a ‘fingerprint’ with PCE 1706 if the same background is shot 30 times: the scene turns into a fingerprint.
Terms: PRNU — Spatial pixel-to-pixel variation in an image sensor’s response under uniform illumination.; PCE — A camera-fingerprint matching score that normalizes the main correlation peak by the correlation energy away from that peak.
↑ The measurement ―― 場面が毎枚違えば生の平均でも指紋は出る。差は PCE の桁に出る。 (figure labels are in Japanese; the numbers are the same)
↑ 別カメラのピークは毎回別の位置に立つ((0,0) は 0/30)。
↑ この回の図
↑ 幾何の上限 = K 自身を同じ往復に通した相関²。
↑ この回の図
py -3.11 examples/poc_prnu_camera_fingerprint.py
Source: examples/poc_prnu_camera_fingerprint.py
This run produced 12 figures in total - see them all
Ops used (notes): aug_jpeg_blocks · evidence_quantile · fingerprint_correlate · gauss_image · median_image · null_distribution · reflect · sensor_fingerprint · sk_nlm · sk_tv · sk_wavelet · xsp_dct_denoise · xsp_wiener
A synthetic car box filmed by a monocular camera 45 m away (960 × 540, 30 fps, one pixel ≈ 7.5 cm) is tracked by its bounding box and scaled by the known car size; the planar track sits 2.8 cm (median) from the truth. Fitting gravity + drag against gravity + drag + constant thrust and choosing by the AIC difference classifies 79 of 80 clips (40 ballistic, 40 thrusted): thrusted ΔAIC is at least 382, while one ballistic clip reaches 10.4 and is misjudged (noise alone scatters around −4 with a χ² width). Thrust magnitude comes back with a median estimate/truth ratio of 1.022, and 2.5 % of g (0.25 m/s²) is still visible with a median ΔAIC of 79. Stretching the clock by k makes g look like g/k², so k is inverted in closed form: at most 0.32 % error for k in [0.5, 4], position 1.000 against a 200-clip clean null, and 2 false positives in 100 further clean clips. A ballistic clip slowed 1.5× reads as an upward thrust of 5.46 m/s² (closed form g(1 − 1/k²) = 5.45): vertical thrust and time stretch are degenerate in a monocular view. No learning, no real footage.
↑ Physical Consistency of Footage — Ballistic or Thrusted, and Was the Clock Stretched? ―― A synthetic car box filmed by a monocular camera 45 m away (960 × 540, 30 fps, one pixel ≈ 7.5 cm) is tracked by its bounding box and scaled by the known car size; the planar track sits 2.8 cm (median) from the truth. Fitting gravity + drag against gravity + drag + constant thrust and choosing by the AIC difference classifies 79 of 80 clips (40 ballistic, 40 thrusted): thrusted ΔAIC is at least 382, while one ballistic clip reaches 10.4 and is misjudged (noise alone scatters around −4 with a χ² width). Thrust magnitude comes back with a median estimate/truth ratio of 1.022, and 2.5 % of g (0.25 m/s²) is still visible with a median ΔAIC of 79. Stretching the clock by k makes g look like g/k², so k is inverted in closed form: at most 0.32 % error for k in [0.5, 4], position 1.000 against a 200-clip clean null, and 2 false positives in 100 further clean clips. A ballistic clip slowed 1.5× reads as an upward thrust of 5.46 m/s² (closed form g(1 − 1/k²) = 5.45): vertical thrust and time stretch are degenerate in a monocular view. No learning, no real footage.
↑ The measurement ―― 推力 4 m/s² の車を 45 m 先の単眼カメラで写した合成映像(960 × 540、30 fps)。箱の影絵と bbox、bbox と車の寸法から起こした位置の軌跡(点)、地面の線。 (figure labels are in Japanese; the numbers are the same)
↑ 点は bbox から、破線は世界の真値。
↑ 雑音だけなら ΔAIC は −2Δk = −4 を中心に χ²(2) の幅で散る。
↑ g の 2.5 % の推力でも床の 10 を超える。
↑ k = √(g_ref / g_fit)。
py -3.11 examples/poc_video_physics_consistency.py
Source: examples/poc_video_physics_consistency.py
This run produced 7 figures in total - see them all
Ops used (notes): aic_from_rss · ball_params · ballistic_vs_thrust · convex_hull · drag_fit · evidence_quantile · fit_parabola · flight_ode · label_extent · null_distribution · project · text_box · thrust_fit · thrust_ode · time_stretch_from_gravity
These runs hang in the autonomous-driving series, not in the metrology museum. The dividing line is where the ground truth comes from: a metrology exhibit measures an object and the truth lives in that object, while here the truth comes from a theorem of car kinematics, from a second implementation that assumes nothing about the closed form, or from a dataset’s published value. That article is written by hand, so the generator does not render this lane.
| There are theorems for the shortest path of a front-steered car (minimum turning radius ρ) from pose (x, y, θ) to pose (x’, y’, θ’). Forward only: Dubins (1957), three segments (arc, line, arc) from six words. With reversing: Reeds and Shepp (1990), at most five segments from 48 words (46 by Sussmann and Tang 1991). Both have closed-form segment lengths, returned by the new ops car_dubins_path and car_reeds_shepp_path. With obstacles, car_hybrid_astar: A* over motion primitives {left, straight, right} × {forward, reverse} on an occupancy grid, pruned per discrete cell (x, y, θ), with the obstacle-free Reeds–Shepp length as an admissible heuristic and an analytic shot from each node to the goal (Dolgov et al. 2010). Correctness: every closed-form candidate is verified by forward integration and discarded if it misses the goal, and the count is returned — over 400 random pairs, 0 rejected, 6.6 RS candidates per pair, and all 18 word families appear both as candidates and as the optimum. A second implementation that solves each word’s endpoint equations for the segment lengths by SLSQP from many starts (assuming nothing about the closed-form structure) matches the closed form to 1e-6 on 3 pairs × RS / Dubins (RS 10.132129 / 6.682896 / 3.322408; Dubins 10.132129 / 8.756928 / 4.552667). The theorem’s inequalities and symmetries — Euclidean distance ≤ RS ≤ Dubins, reversibility L(s, g) = L(g, s), reflection, rigid-motion invariance, scaling with ρ, the triangle inequality — hold on 400 pairs + 200 triples; Dubins − RS is at most 10.647 m (what reversing buys). For an aligned goal RS = Dubins = distance with word S. On an obstacle-free 40 × 30 m grid Hybrid A* costs 23.353319 m, exactly the RS length 23.353319 m (1 expansion, the first shot goes through), and forward-only equals the Dubins length. Parallel parking (18 × 8 m, cell 0.25 m, 72 heading bins, car 4.5 × 1.8 m, ρ 5.5 m, a 7.5 m bay) costs 16.018 m ≥ the lower bound 7.986 m (the obstacle-free RS path hits the parked cars), 3,385 expansions, 4,203 generated, 42 segments (12 reversed, 8 gear changes), 0.8 s, and the returned poses are collision-free with the rectangular body and satisfy | Δθ | ≤ Δs/ρ. Walling the bay off raises ValueError (no partial path). 16.3 s in all. Honestly: a 6.5 m bay (car length + 2.0 m) was unreachable at this discretisation. Copying the RS formulas with the wrong mod2pi convention ((−π, π] vs [0, 2π)) makes the eight CCSC words never appear — the gate that counts word families caught it. |
| ↑ How a Car Turns Shortest — Dubins and Reeds–Shepp in Closed Form, and Hybrid A* Parallel Parking on an Occupancy Grid ―― There are theorems for the shortest path of a front-steered car (minimum turning radius ρ) from pose (x, y, θ) to pose (x’, y’, θ’). Forward only: Dubins (1957), three segments (arc, line, arc) from six words. With reversing: Reeds and Shepp (1990), at most five segments from 48 words (46 by Sussmann and Tang 1991). Both have closed-form segment lengths, returned by the new ops car_dubins_path and car_reeds_shepp_path. With obstacles, car_hybrid_astar: A over motion primitives {left, straight, right} × {forward, reverse} on an occupancy grid, pruned per discrete cell (x, y, θ), with the obstacle-free Reeds–Shepp length as an admissible heuristic and an analytic shot from each node to the goal (Dolgov et al. 2010). Correctness: every closed-form candidate is verified by forward integration and discarded if it misses the goal, and the count is returned — over 400 random pairs, 0 rejected, 6.6 RS candidates per pair, and all 18 word families appear both as candidates and as the optimum. A second implementation that solves each word’s endpoint equations for the segment lengths by SLSQP from many starts (assuming nothing about the closed-form structure) matches the closed form to 1e-6 on 3 pairs × RS / Dubins (RS 10.132129 / 6.682896 / 3.322408; Dubins 10.132129 / 8.756928 / 4.552667). The theorem’s inequalities and symmetries — Euclidean distance ≤ RS ≤ Dubins, reversibility L(s, g) = L(g, s), reflection, rigid-motion invariance, scaling with ρ, the triangle inequality — hold on 400 pairs + 200 triples; Dubins − RS is at most 10.647 m (what reversing buys). For an aligned goal RS = Dubins = distance with word S. On an obstacle-free 40 × 30 m grid Hybrid A* costs 23.353319 m, exactly the RS length 23.353319 m (1 expansion, the first shot goes through), and forward-only equals the Dubins length. Parallel parking (18 × 8 m, cell 0.25 m, 72 heading bins, car 4.5 × 1.8 m, ρ 5.5 m, a 7.5 m bay) costs 16.018 m ≥ the lower bound 7.986 m (the obstacle-free RS path hits the parked cars), 3,385 expansions, 4,203 generated, 42 segments (12 reversed, 8 gear changes), 0.8 s, and the returned poses are collision-free with the rectangular body and satisfy | Δθ | ≤ Δs/ρ. Walling the bay off raises ValueError (no partial path). 16.3 s in all. Honestly: a 6.5 m bay (car length + 2.0 m) was unreachable at this discretisation. Copying the RS formulas with the wrong mod2pi convention ((−π, π] vs [0, 2π)) makes the eight CCSC words never appear — the gate that counts word families caught it.* |
↑ The measurement ―― 小文字 = 後退の区間。後退を許すと Dubins より必ず短いか等しい(400 対で確認、最大差 10.647)。ρ = 1 m。 (figure labels are in Japanese; the numbers are the same)
↑ 縦列駐車の Hybrid A: 灰 = 障害物(縁石・駐車車両 2 台・壁)、薄緑の点 = 展開した 4203 姿勢、赤 = 前進、青 = 後退。*
↑ RS 最適化器 1 10.1321; Dubins 最適化器 1 10.1321; RS 最適化器 2 6.6829; Dubins 最適化器 2 8.7569; RS 最適化器 3 3.3224; Dubins 最適化器 3 4.5527; 整列 5 m (Dubins) 5.0000; 整列 5…
↑ The animation ―― 車(橙)が Hybrid A の道を辿って 2 台の間に入る。52 コマ、前進 30 区間・後退 12 区間、切替 8 回。*
py -3.11 examples/poc_car_parking.py
Source: examples/poc_car_parking.py
This run produced 5 figures in total - see them all
Ops used (notes): arrow · car_dubins_path · car_hybrid_astar · car_reeds_shepp_path
Self-driving demos move, so nobody measures whether they are right. Measuring needs a world that carries its own ground truth. A regulation loop course (oval, 80 m straights, 8 m wide) is built to the dimensions of Appendix 3 of the Road Traffic Act Enforcement Regulations (ordinary licence), with a cross of main roads (7 m, four signals) and the test elements inside — crank (3.5 m wide, 12 m between bends, 1 m fillet), S-curve (3.5 m, outer radius 7.5 m, 3/8 of a circle), slope (8 % / 11 %), parallel parking, turnaround, level crossing (1.1 m gauge) — whose exits rejoin the loop (drivecourse; truth = closed-form polygon areas). The 2-D course becomes a 3-D world with CC0 cars and signals and signs generated procedurally to Japanese standard dimensions (driveworld / roadjp; every face carries a label and a colour), a spinning LiDAR is fired at the mesh (lidarsim; Möller–Trumbore accelerated by azimuth/elevation bins, closed-form plane and box hits as the second implementation) and an in-car camera renders the same faces. Fourteen gates: polygon areas converge monotonically to the closed forms, flat-ground ranges equal h/(−sin e) to 1e-9, LiDAR points projected into the camera agree with the pixel depth and label (two sensors, one world), car points fall inside the placed car boxes (100 %), kerb points build an occupancy grid that is a subset of the true one, the Hybrid A* path (round 13) keeps every corner within half a cell of the kerb line, and the camera reads the signal as red or green (and cannot when the lamps are off). Honestly: the regulation 3.5 m width is unreachable forward-only for a 4.5 × 1.8 m car with max-steer-and-straight primitives; allowing reversing gets through.
↑ The Driving School Opens — Cars and Signals on a Regulation Loop Course, Seen by LiDAR (laser ranging) and Camera, Scored by Theorems and Identities ―― Self-driving demos move, so nobody measures whether they are right. Measuring needs a world that carries its own ground truth. A regulation loop course (oval, 80 m straights, 8 m wide) is built to the dimensions of Appendix 3 of the Road Traffic Act Enforcement Regulations (ordinary licence), with a cross of main roads (7 m, four signals) and the test elements inside — crank (3.5 m wide, 12 m between bends, 1 m fillet), S-curve (3.5 m, outer radius 7.5 m, 3/8 of a circle), slope (8 % / 11 %), parallel parking, turnaround, level crossing (1.1 m gauge) — whose exits rejoin the loop (drivecourse; truth = closed-form polygon areas). The 2-D course becomes a 3-D world with CC0 cars and signals and signs generated procedurally to Japanese standard dimensions (driveworld / roadjp; every face carries a label and a colour), a spinning LiDAR is fired at the mesh (lidarsim; Möller–Trumbore accelerated by azimuth/elevation bins, closed-form plane and box hits as the second implementation) and an in-car camera renders the same faces. Fourteen gates: polygon areas converge monotonically to the closed forms, flat-ground ranges equal h/(−sin e) to 1e-9, LiDAR points projected into the camera agree with the pixel depth and label (two sensors, one world), car points fall inside the placed car boxes (100 %), kerb points build an occupancy grid that is a subset of the true one, the Hybrid A path (round 13) keeps every corner within half a cell of the kerb line, and the camera reads the signal as red or green (and cannot when the lamps are off). Honestly: the regulation 3.5 m width is unreachable forward-only for a 4.5 × 1.8 m car with max-steer-and-straight primitives; allowing reversing gets through.*
Terms: LiDAR — A sensor that measures range using laser light, commonly by time of flight, to produce a three-dimensional point cloud.
↑ The measurement ―― 同じ世界を斜めから: CC0 の車・信号機・標識・コーン(Kenney)を実寸に合わせて置き、縁石(高さ 0.15 m)と白線を多角形の縁に沿って生成(継ぎ目には置かない)。面ごとにラベルと色を持つので、センサの真値は世界の側にある。 (figure labels are in Japanese; the numbers are the same)
↑ 停止線の 10 m 手前での LiDAR 一掃(32 ビーム・0.5°、13691 点)を面のラベルで塗る: 灰 = 路面、黄 = 縁石、赤 = 車、緑 = 信号機、青 = 標識、橙 = コーン、白 = 白線。
↑ 同じ瞬間の車載カメラ(60°)に LiDAR の点を投影して重ねる: 点の深度と画素の深度の相対差は中央値 3.00e-03、ラベル一致 100.0 %(1 画素の許容; 門 3、2 センサ 1 世界の恒等式)。
↑ 真値の散布: 8 種の要素の面積(靴紐 vs 閉形式)と路面の range 200 点(実測 vs h/(−sin e))。
↑ The animation ―― 幹線で信号を待ち、車載カメラの画像処理(右上の ROI: 地図の信号位置を投影し色度で点灯を検出)が ‘green’ を読んだ次のコマで発進して交差点を渡り、クランクを抜けて連絡路から周回コースへ合流する(69 コマ、赤で 6 コマ待ち)。追走カメラの画像に LiDAR(16 ビーム)の点を重ねる。クランクは前進のみでは到達不能で、切り返し 14 回(後退の区間数)。全姿勢で隅の越え幅は半セル以内。
py -3.11 examples/poc_driving_school.py
Source: examples/poc_driving_school.py
This run produced 6 figures in total - see them all
Ops used (notes): add_sign · ball_detect · car_dubins_path · car_hybrid_astar · course_contains · course_crank · course_crossing · course_intersection · course_layout · course_loop · course_loop_bend · course_occupancy · course_parallel_parking · course_road · course_s_curve · course_slope · course_turnaround · intersection · lidar_scan · lidar_spec · load_asset · polygon_area · resample · text_box … (+2)
A car is coming the other way. How many seconds until impact (τ, Lee 1976) follows from how fast its image grows alone; how many metres to keep is what RSS (Shalev-Shwartz et al. 2017) gives in closed form from response time and acceleration bounds. On the driving-school loop (80 m straight, left-hand traffic) the ego car (8 m/s) films an oncoming car at the same speed with an in-car camera (60°, 640 × 400, 10 Hz), and τ is produced three ways: per pixel in closed form from the depth image and the rigid motion between two frames (truth), from optical flow (Lucas–Kanade, 5 levels) through time_to_contact (radius from the FoE over radial flow), and from the change of the apparent area’s square root. Identity: for pure translation the true flow yields the τ one frame later, and adding one frame matches the true τ₀ to 1e-9 (every frame, every pixel). True τ falls from 5 s at exactly one second per second (slope −1). Flow-based τ sits on the truth while the car covers ≥ 150 px and drifts off at range where the flow is sub-pixel (reported honestly). RSS uses the ad-rss-lib parameter table (ρ 1/2 s, accel 3.5, braking 4/8/3, lateral 0.2/0.8, μ 0.1 m), matches its five lateral test values to ±0.01 and the published ≈ 40 / ≈ 80 m at 50 km/h. Lemma 2 agrees with a time integration of the worst case (lead brakes hard, follower accelerates for the response time then brakes) to 1e-6, with exactly zero gap at d₀ = d_min. In the school, braking the instant RSS flags the stopped car ahead leaves a final gap equal to gap(t_b) − (vρ + v²/2b) to 1e-9. The oncoming car in the other lane pushes τ down to 0.3 s yet RSS never flags it, because the lateral safe distance 0.73 m is below the 2.2 m lane gap; when it drifts over at 0.6 m/s the lateral check fails while the longitudinal gap is already inside the 83 m opposite-direction distance, and the worst case collides — the blame is on the drifting car.
↑ Time to Collision and Safe Distance — Optical-Flow τ and the RSS Closed Forms, Scored by the Driving-School World’s Truth ―― A car is coming the other way. How many seconds until impact (τ, Lee 1976) follows from how fast its image grows alone; how many metres to keep is what RSS (Shalev-Shwartz et al. 2017) gives in closed form from response time and acceleration bounds. On the driving-school loop (80 m straight, left-hand traffic) the ego car (8 m/s) films an oncoming car at the same speed with an in-car camera (60°, 640 × 400, 10 Hz), and τ is produced three ways: per pixel in closed form from the depth image and the rigid motion between two frames (truth), from optical flow (Lucas–Kanade, 5 levels) through time_to_contact (radius from the FoE over radial flow), and from the change of the apparent area’s square root. Identity: for pure translation the true flow yields the τ one frame later, and adding one frame matches the true τ₀ to 1e-9 (every frame, every pixel). True τ falls from 5 s at exactly one second per second (slope −1). Flow-based τ sits on the truth while the car covers ≥ 150 px and drifts off at range where the flow is sub-pixel (reported honestly). RSS uses the ad-rss-lib parameter table (ρ 1/2 s, accel 3.5, braking 4/8/3, lateral 0.2/0.8, μ 0.1 m), matches its five lateral test values to ±0.01 and the published ≈ 40 / ≈ 80 m at 50 km/h. Lemma 2 agrees with a time integration of the worst case (lead brakes hard, follower accelerates for the response time then brakes) to 1e-6, with exactly zero gap at d₀ = d_min. In the school, braking the instant RSS flags the stopped car ahead leaves a final gap equal to gap(t_b) − (vρ + v²/2b) to 1e-9. The oncoming car in the other lane pushes τ down to 0.3 s yet RSS never flags it, because the lateral safe distance 0.73 m is below the 2.2 m lane gap; when it drifts over at 0.6 m/s the lateral check fails while the longitudinal gap is already inside the 83 m opposite-direction distance, and the worst case collides — the blame is on the drifting car.
↑ The measurement ―― t = 4 s の車載カメラ(60°, 640 × 400)。対向車の画素の光学流(Lucas–Kanade 5 段、×3 で描く)は真の FoE(十字)から外へ向かい、その半径方向の速さから τ = 0.84 s(真値 0.86 s)。路面には模様が無いので流れは車と街灯にしか無い。 (figure labels are in Japanese; the numbers are the same)
↑ 対向車の τ(衝突までの時間)の 4 本: 真値は 5 s から 1 秒に 1 秒ずつ減る(傾き −1)。
↑ 場面 A: 停車車両への間隔(青)が RSS の安全距離(橙、v = 8 で 26.28 m)を割った t = 6.0 s に「危険」→ 応答時間 1 s は速度維持、その後 4 m/s² で制動。
↑ 場面 B: 対向車線なら横の間隔 2.2 m > 横の安全距離 0.72 m で、τ が 0.3 s まで落ちても RSS は危険を出さない。
↑ The animation ―― 車載カメラで 0 → 10 s(51 コマ): 対向車が来る間はその画素の光学流(矢印 ×4、長さ 40 px まで)と真の FoE(十字)を描き、τ の真値と流れからの τ を並べる。通り過ぎたあと自車線の停車車両に RSS が「危険」を出した瞬間(t = 6.0 s)に制動して 10.25 m 手前で止まる。
py -3.11 examples/poc_ttc_rss.py
Source: examples/poc_ttc_rss.py
This run produced 6 figures in total - see them all
Ops used (notes): arrow · course_layout · course_loop · crosshair · flow_from_depth_motion · foe_from_motion · label_extent · load_asset · relative_motion · rss_lateral · rss_lateral_check · rss_longitudinal_check · rss_longitudinal_opposite · rss_longitudinal_same · rss_params · rss_worst_case_gap · rss_worst_case_gap_opposite · text_box · ttc_from_flow · ttc_from_range · ttc_from_scale · ttc_truth · world_build · world_camera … (+1)
| In the previous exhibit the world ended 8 m outside the course and the road had no texture, so the focus of expansion (FoE) of optical flow had to be taken as known. This exhibit widens the world while every part of it keeps its truth from the moment it is generated. Terrain is a sum of random-phase sinusoids (spectral synthesis of fBm, Saupe 1988) with height and gradient in closed form; the 512 × 512 periodogram slope is β̂ = 3.59 against the theorem 2H + 2 = 3.6 (H = 0.8). The distance field to the course is the point–segment closed form with | ∇d | = 1 off the road (eikonal); the road is flat within 2 m, blends into the relief over 12 m, and undulates with 0.8 m amplitude. Materials are evaluated in world coordinates recovered from the rendered depth, using Perlin’s (2002) gradient noise (zero at lattice points, period 256, analytic derivatives): asphalt grain, grass, dark stains, puddles (reflectance is truth) and worn lane lines (wear is truth). Road pixels seen from the in-car camera match the closed-form height to 0.4 mm at the median and 13.0 cm at the 99th percentile (the chord of the 2 m mesh), every puddle pixel lies inside the course, and line colours equal the wear mix times shading to 0. Seventy trees (prism + cone / ellipsoid of revolution; the conifer volume matches the divergence-theorem mesh volume to 1e-9, the broadleaf crown is inscribed at 0.88 of its bound) are scattered ≥ 4 m from the road and ≥ 5 m apart, the crosswalk has 9 stripes = 16.2 m², and a pedestrian (boxes + a solid of revolution) crosses it. Over 10 s of driving up and down the undulation (pairs of frames at 30 fps) the FoE from the true flow matches foe_from_motion to 6e-14 px (pure-translation flow is radial from the FoE). The FoE estimated by least squares from the Lucas–Kanade flow of the road pixels alone (distance from point to flow line, weighted by angular residual) is 1.9 px off at the median with texture (1.2 px using all pixels) and 57 px off when the same road is left plain. Honestly: worn lines and puddles are misclassified by the best brightness threshold in either direction 25 % of the time — that is the point of the hard objects, and the truth stays exact per pixel. Trees and pedestrians are boxes and solids of revolution; the puddle reflection is a sky-colour mix. |
| *↑ Widening the World — Closed-Form Terrain, World-Space Materials and Procedural Trees and Pedestrians Win the Focus of Expansion Back from Flow ―― In the previous exhibit the world ended 8 m outside the course and the road had no texture, so the focus of expansion (FoE) of optical flow had to be taken as known. This exhibit widens the world while every part of it keeps its truth from the moment it is generated. Terrain is a sum of random-phase sinusoids (spectral synthesis of fBm, Saupe 1988) with height and gradient in closed form; the 512 × 512 periodogram slope is β̂ = 3.59 against the theorem 2H + 2 = 3.6 (H = 0.8). The distance field to the course is the point–segment closed form with | ∇d | = 1 off the road (eikonal); the road is flat within 2 m, blends into the relief over 12 m, and undulates with 0.8 m amplitude. Materials are evaluated in world coordinates recovered from the rendered depth, using Perlin’s (2002) gradient noise (zero at lattice points, period 256, analytic derivatives): asphalt grain, grass, dark stains, puddles (reflectance is truth) and worn lane lines (wear is truth). Road pixels seen from the in-car camera match the closed-form height to 0.4 mm at the median and 13.0 cm at the 99th percentile (the chord of the 2 m mesh), every puddle pixel lies inside the course, and line colours equal the wear mix times shading to 0. Seventy trees (prism + cone / ellipsoid of revolution; the conifer volume matches the divergence-theorem mesh volume to 1e-9, the broadleaf crown is inscribed at 0.88 of its bound) are scattered ≥ 4 m from the road and ≥ 5 m apart, the crosswalk has 9 stripes = 16.2 m², and a pedestrian (boxes + a solid of revolution) crosses it. Over 10 s of driving up and down the undulation (pairs of frames at 30 fps) the FoE from the true flow matches foe_from_motion to 6e-14 px (pure-translation flow is radial from the FoE). The FoE estimated by least squares from the Lucas–Kanade flow of the road pixels alone (distance from point to flow line, weighted by angular residual) is 1.9 px off at the median with texture (1.2 px using all pixels) and 57 px off when the same road is left plain. Honestly: worn lines and puddles are misclassified by the best brightness threshold in either direction 25 % of the time — that is the point of the hard objects, and the truth stays exact per pixel. Trees and pedestrians are boxes and solids of revolution; the puddle reflection is a sky-colour mix.* |
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_world_terrain/02_terrain_spectrum.png)
↑ The measurement ―― fBm の面(H = 0.8)の動径周期図は log–log で直線: 傾き β̂ = 3.59、定理(Saupe 1988: β = 2H + E、E = 2)は 3.6。帯域 [1/100, 1/10] 周期/m の 12 帯で当てはめ。有限の帯域と Hann 窓で +0.1 ほど急に出る。 (figure labels are in Japanese; the numbers are the same)
↑ 車載カメラ(60°, 640 × 400)の 1 コマと、その画素ごとの真値。
↑ t = 1.2 s(誤差が中央値に最も近い典型的なコマ)の車載カメラ(480 × 300、30 fps の 2 コマ)。
↑ 拡大の中心の推定誤差(真値との距離)。
↑ The animation ―― 車載カメラで 0 → 10 s(51 コマ、6 m/s): 起伏の中の周回コースを坂を上り下りしながら走る。矢印は LK の流れ(×4)、橙の十字は流れから推定した拡大の中心、緑は真値。横断歩道を歩行者が渡り、路面には水溜りと摩耗した白線。
py -3.11 examples/poc_world_terrain.py
Source: examples/poc_world_terrain.py
This run produced 6 figures in total - see them all
Ops used (notes): add_mesh_object · add_sign · course_contains · course_distance · course_layout · course_loop · crosshair · crosswalk_mesh · fbm_gradient · fbm_height · flow_from_depth_motion · foe_from_flow · foe_from_motion · material_params · mesh_signed_volume · pedestrian_mesh · perlin2 · radial_periodogram · relative_motion · scatter_offroad · spectral_slope · terrain_gradient · terrain_height · terrain_params … (+7)
| The school car so far moved as a list of poses every 2.5 m and stopped dead at the stop line — no time, no speed (the user: “does the car obey inertia, does braking distance depend on speed, are there slopes?”). This exhibit gives the car longitudinal motion: m dv/dt = drive − brake − m g sin θ − c_rr m g cos θ − ½ρC_dA v | v | , and at rest it stays put as long as the brake can hold it. Three truths: the closed form of the stopping distance vρ + (1/2k)ln(1 + k v²/A) (A = b ± g sin θ + c_rr g cos θ; v²/2A without drag) matches the integrator on flat, uphill and downhill × 20/40/60 km/h (15 cases) to 8.5e-14, and the separately written rsssafety stopping distance on flat ground to 5.5e-13; the hill-start roll-back ½a₁τ² + (a₁τ)²/(2a₂) (τ from brake release to drive) matches to 1.8e-15; the energy balance (½v² + g z + rolling, drag and brake work − drive work) holds to 1.2e-9 J/kg throughout. Closed loop: the in-car camera reads the signal (117 / 117 correct within the 20 m it trusts), and on reading amber the car covers the reaction distance, brakes in two stages (the notice deducts for not braking in several steps) and stops 0.500 m before the line, does not move a millimetre on red, and starts after reading green. On the slope course (8 % up) the stop and start roll back 0 m, and the 11 % descent stays under 15 km/h. Scored with the National Police Agency notice (No. 12, 2022): no deductions, 100 points. Knobs are read against closed-form thresholds: a reaction time beyond 2.546 s crosses the line (improper stop position), a road μ below 0.1134 cannot stop in time (rain plugs in here — next part), and a clumsy start with a 1 s pedal change rolls back a little at 9 %, moderately at 10–11 % and fails the test at 12.5 % and above. With the lamps off the reading stays ‘unknown’ and the car stops and does not start (fail-closed). Honestly: the roll-back distances (0.3 / 0.5 / 1 m) and “2 m or more short of the line is improper” are not in the notice (secondary sources); mass 1300 kg, braking limit and a 0.75 s reaction are assumptions; the stop is planned at the moment of perception and not re-measured (world and model agree, so it stops exactly); the slope’s kerbs are drawn at ground level (a driveworld issue). 14 gates, 28.0 s. |
| *↑ Giving the Car Inertia and Slopes — Stopping Just Before the Line with Reaction and Braking Distance, and a Hill Start Without Rolling Back ―― The school car so far moved as a list of poses every 2.5 m and stopped dead at the stop line — no time, no speed (the user: “does the car obey inertia, does braking distance depend on speed, are there slopes?”). This exhibit gives the car longitudinal motion: m dv/dt = drive − brake − m g sin θ − c_rr m g cos θ − ½ρC_dA v | v | , and at rest it stays put as long as the brake can hold it. Three truths: the closed form of the stopping distance vρ + (1/2k)ln(1 + k v²/A) (A = b ± g sin θ + c_rr g cos θ; v²/2A without drag) matches the integrator on flat, uphill and downhill × 20/40/60 km/h (15 cases) to 8.5e-14, and the separately written rsssafety stopping distance on flat ground to 5.5e-13; the hill-start roll-back ½a₁τ² + (a₁τ)²/(2a₂) (τ from brake release to drive) matches to 1.8e-15; the energy balance (½v² + g z + rolling, drag and brake work − drive work) holds to 1.2e-9 J/kg throughout. Closed loop: the in-car camera reads the signal (117 / 117 correct within the 20 m it trusts), and on reading amber the car covers the reaction distance, brakes in two stages (the notice deducts for not braking in several steps) and stops 0.500 m before the line, does not move a millimetre on red, and starts after reading green. On the slope course (8 % up) the stop and start roll back 0 m, and the 11 % descent stays under 15 km/h. Scored with the National Police Agency notice (No. 12, 2022): no deductions, 100 points. Knobs are read against closed-form thresholds: a reaction time beyond 2.546 s crosses the line (improper stop position), a road μ below 0.1134 cannot stop in time (rain plugs in here — next part), and a clumsy start with a 1 s pedal change rolls back a little at 9 %, moderately at 10–11 % and fails the test at 12.5 % and above. With the lamps off the reading stays ‘unknown’ and the car stops and does not start (fail-closed). Honestly: the roll-back distances (0.3 / 0.5 / 1 m) and “2 m or more short of the line is improper” are not in the notice (secondary sources); mass 1300 kg, braking limit and a 0.75 s reaction are assumptions; the stop is planned at the moment of perception and not re-measured (world and model agree, so it stops exactly); the slope’s kerbs are drawn at ground level (a driveworld issue). 14 gates, 28.0 s.* |
↑ The measurement ―― 速さ・路面の高さ・ブレーキの時系列。交差点では反応(空走)の間は速さを保ち、2 段で止まる(制動の 2 つの段)。赤の間は 0、青を読んで発進。坂で一時停止して発進し、急な下りでは制動で速さを保つ。 (figure labels are in Japanese; the numbers are the same)
↑ 停止距離 = 空走 vρ + 制動距離(制動 4 m/s²、反応 0.75 s)。
↑ つまみ = 反応時間: 同じ場所で黄を読んでも、反応が遅いと空走が延び、閉形式のしきい値 ρ = 2.55 s を越えると上限のブレーキでも停止線を越える(停止位置不適 / 信号無視)。*
↑ つまみ = 勾配: ブレーキを離してからアクセルまで 1.0 s かかる下手な坂道発進のずり下がり。
py -3.11 examples/poc_driving_longitudinal.py
Source: examples/poc_driving_longitudinal.py
This run produced 5 figures in total - see them all
Ops used (notes): ball_detect · course_intersection · course_layout · course_road · course_slope · hill_hold_brake_min · hill_start_command · hill_start_rollback · load_asset · long_energy_residual · long_params · long_simulate · plan_command · road_eval · road_profile · rss_stopping_distance · skill_test_score · stop_line_plan · stopping_distance_grade · text_box · world_build · world_camera
Autonomous cars drive in sunlight and weather (the user: “it would be nice to reproduce sunlight and weather too”). This exhibit relights the driving-school world in physical units (luminance cd/m², illuminance lx) and, for the in-car camera’s image processing (reading lamps by chromaticity), computes in closed form from the pixel equation where reading fails, then checks that the rendered reading changes on either side. Sun: elevation and azimuth from date, time and position (NOAA). The 24 values published by the NAOJ Ephemeris Computation Office for Tokyo in 2026 (equinoxes and solstices: sunrise, transit and sunset times, azimuths and altitude) agree to the rounding unit (1 min, 0.1°). Shadow tips agree with h cot(elevation) within a pixel at four elevations. Backlight: the veiling luminance L_v = 10E/θ² (Stiles–Holladay) is white and pulls a lamp’s chromaticity toward grey. From the white W* at which chromaticity leaves the detector’s tolerance, the threshold θ* = √(10E/W) predicts read / no-read over a sweep of 3 colours × sun angles with no disagreement. On the March equinox an eastbound car cannot read red during 05:53〜07:05 (predicted from the sun equations, confirmed by rendering). Fog: Koschmieder’s law. Fitting the road’s vertical luminance profile recovers the visibility from the image (0.96 % error for 30–200 m, 1 % noise). The distance at which a lamp’s colour can be read, ln(1 + W/L_h)/β (L_h = airlight measured from the image’s sky), matches the rendered reading within one sweep step. At 200 m visibility red becomes readable 6.6 m before the line, so the speed that can still stop is 17.5 km/h (the stopping-distance closed form solved for v); at 0.9× the car stops before the line, at 1.25× it crosses (closed loop). Rain: the road-design manual’s stopping distance D = 0.694V + 0.00394V²/f agrees with the previous part’s closed form (second implementation), the wet f lengthens stopping, and the red reflected on the road lies outside the map ROI so it does not disturb reading. Night: with the assumed beam pattern, a pedestrian is found in the image at 60 m (low beam) and 120 m (high beam) (the 40 m / 100 m performance in the vehicle safety standard), and above / below the speed that can stop after detection the car fails / succeeds to stop. With the lamps off every weather reads ‘unknown’ (fail-closed). Honestly: sky and twilight brightness, lamp luminance, headlamp pattern and the HDR tone curve are assumptions; the veil uses the human-eye scattering formula as a stand-in for the lens; fog is measured in the same uniform Koschmieder world it was rendered in; rain streaks and reflection blur are only for looks. 13 gates, 134.9 s.
↑ Sun and Weather — When the Morning Sun Hides the Signal, How Fast You May Drive in Fog, Wet Roads and Headlamps at Night ―― Autonomous cars drive in sunlight and weather (the user: “it would be nice to reproduce sunlight and weather too”). This exhibit relights the driving-school world in physical units (luminance cd/m², illuminance lx) and, for the in-car camera’s image processing (reading lamps by chromaticity), computes in closed form from the pixel equation where reading fails, then checks that the rendered reading changes on either side. Sun: elevation and azimuth from date, time and position (NOAA). The 24 values published by the NAOJ Ephemeris Computation Office for Tokyo in 2026 (equinoxes and solstices: sunrise, transit and sunset times, azimuths and altitude) agree to the rounding unit (1 min, 0.1°). Shadow tips agree with h cot(elevation) within a pixel at four elevations. Backlight: the veiling luminance L_v = 10E/θ² (Stiles–Holladay) is white and pulls a lamp’s chromaticity toward grey. From the white W at which chromaticity leaves the detector’s tolerance, the threshold θ* = √(10E/W) predicts read / no-read over a sweep of 3 colours × sun angles with no disagreement. On the March equinox an eastbound car cannot read red during 05:53〜07:05 (predicted from the sun equations, confirmed by rendering). Fog: Koschmieder’s law. Fitting the road’s vertical luminance profile recovers the visibility from the image (0.96 % error for 30–200 m, 1 % noise). The distance at which a lamp’s colour can be read, ln(1 + W/L_h)/β (L_h = airlight measured from the image’s sky), matches the rendered reading within one sweep step. At 200 m visibility red becomes readable 6.6 m before the line, so the speed that can still stop is 17.5 km/h (the stopping-distance closed form solved for v); at 0.9× the car stops before the line, at 1.25× it crosses (closed loop). Rain: the road-design manual’s stopping distance D = 0.694V + 0.00394V²/f agrees with the previous part’s closed form (second implementation), the wet f lengthens stopping, and the red reflected on the road lies outside the map ROI so it does not disturb reading. Night: with the assumed beam pattern, a pedestrian is found in the image at 60 m (low beam) and 120 m (high beam) (the 40 m / 100 m performance in the vehicle safety standard), and above / below the speed that can stop after detection the car fails / succeeds to stop. With the lamps off every weather reads ‘unknown’ (fail-closed). Honestly: sky and twilight brightness, lamp luminance, headlamp pattern and the HDR tone curve are assumptions; the veil uses the human-eye scattering formula as a stand-in for the lens; fog is measured in the same uniform Koschmieder world it was rendered in; rain streaks and reflection blur are only for looks. 13 gates, 134.9 s.*
↑ The measurement ―― 春分(2026-03-20)の東京、朝 5:15 から夜 20:15 まで 15 分おき。左 = 東へ向かう車の車載カメラ(停止線の 15 m 手前)と信号の読み、右 = 斜め上から(影が太陽と反対へ伸び、短くなってまた伸びる)。朝、太陽が信号の後ろの低い空にある間は光幕で赤の色度が灰色へ寄り、画像処理は「読めない」(05:53〜07:05)。日が沈むと前照灯を点ける。 (figure labels are in Japanese; the numbers are the same)
↑ 東京の太陽の高度(NOAA の式、大気差つき)と国立天文台 暦計算室の公表値(点: 日の出・日の入りは高度 0、南中は高度)。
↑ 逆光: 横 = 太陽と灯火への視線の角 θ、縦 = 画素の式から先に出した閾値 θ = √(10 E_dn / W)(高度で変わる)。
↑ 霧の濃さを画像から測る: 車線の中の路面の輝度を行ごとに並べると(点、雑音 1 %)、遠い行ほど大気光へ近づく。
↑ 霧の中で灯火の色が読める距離: 閉形式 d = ln(1 + W/L_h)/β(線)と、描いた画像で読めた距離(点)。
py -3.11 examples/poc_driving_weather.py
Source: examples/poc_driving_weather.py
This run produced 7 figures in total - see them all
Ops used (notes): ball_detect · beta_from_mor · course_intersection · course_layout · course_road · env_params · env_render · fog_beta_from_profile · load_asset · long_params · long_simulate · pedestrian_mesh · plan_command · road_row_distance · sight_stop_speed · skill_test_score · stop_line_plan · stopping_distance_grade · sun_at · sun_events · sun_illuminance · sun_vector · text_box · veil_chroma_limit … (+1)
The author’s idea: “if the map kept being generated forward, backward, left and right without end, and map beyond a certain distance disappeared automatically, we could test much longer continuous driving.” The world is split into 200 m tiles (i, j) and each tile’s contents come from nothing but its number and the world seed (a SplitMix64 hash, no random state carried over). Whether and where a road crosses each tile edge comes from a hash of the edge number, so both neighbours read the same value and roads always meet at the seam. Relief is Perlin noise on one integer lattice for the whole world (tile number × cells + cell index), interpolated with only the fraction inside the tile, so it is continuous across seams and never loses digits far away. Positions are held as (tile, coordinates inside the tile), and drawing uses the car’s tile as the origin (a floating origin). Only the 5 × 5 tiles around the car are held while it drives 50 km, eastward-biased, starting from a tile 1,000 km out. Gates: seams (300 tile pairs, numbers up to ±10⁶) differ in height by 3.3e-15 m and road crossings agree bit for bit / generation order does not matter / the 214 tiles dropped and remade match their first fingerprints (SHA-256) / at most 25 tiles are held / the car never leaves the road centreline / tile coordinates are off by 2e-11 m after 50 km while a float32 global coordinate drifts by 33.42 m (float32 steps are 62.5 mm 1,000 km out). Found on the way: if the flat band around a road is too narrow, ground triangles rise above the road and bury it (the band is half the road width plus the lattice diagonal, about 18 m); the renderer drops any triangle with a vertex behind the camera, so 100 m road strips vanished entirely (strips are cut at the lattice spacing). Honestly: roads are straight polylines from edges to a junction; no buildings, traffic or signs; relief is Perlin only. 6 gates, 26.2 s.
↑ An Endless Map — Tiles Made Around the Car, Far Tiles Dropped, 50 km Without a Break ―― The author’s idea: “if the map kept being generated forward, backward, left and right without end, and map beyond a certain distance disappeared automatically, we could test much longer continuous driving.” The world is split into 200 m tiles (i, j) and each tile’s contents come from nothing but its number and the world seed (a SplitMix64 hash, no random state carried over). Whether and where a road crosses each tile edge comes from a hash of the edge number, so both neighbours read the same value and roads always meet at the seam. Relief is Perlin noise on one integer lattice for the whole world (tile number × cells + cell index), interpolated with only the fraction inside the tile, so it is continuous across seams and never loses digits far away. Positions are held as (tile, coordinates inside the tile), and drawing uses the car’s tile as the origin (a floating origin). Only the 5 × 5 tiles around the car are held while it drives 50 km, eastward-biased, starting from a tile 1,000 km out. Gates: seams (300 tile pairs, numbers up to ±10⁶) differ in height by 3.3e-15 m and road crossings agree bit for bit / generation order does not matter / the 214 tiles dropped and remade match their first fingerprints (SHA-256) / at most 25 tiles are held / the car never leaves the road centreline / tile coordinates are off by 2e-11 m after 50 km while a float32 global coordinate drifts by 33.42 m (float32 steps are 62.5 mm 1,000 km out). Found on the way: if the flat band around a road is too narrow, ground triangles rise above the road and bury it (the band is half the road width plus the lattice diagonal, about 18 m); the renderer drops any triangle with a vertex behind the camera, so 100 m road strips vanished entirely (strips are cut at the lattice spacing). Honestly: roads are straight polylines from edges to a junction; no buildings, traffic or signs; relief is Perlin only. 6 gates, 26.2 s.
↑ The measurement ―― 追従カメラ(赤 = 車、25 m ごと、最初の 3 km)。持っている 25 区画だけを、車のいる区画の原点に合わせて描く(浮動原点)—— 1,000 km 先でも頂点の座標は ±600 m に収まる。区画の継ぎ目で道と地面は途切れない。描画器はカメラの後ろに頂点がある三角形を捨てるので、足元の穴は地面の色で埋めている(見た目だけ)。 (figure labels are in Japanese; the numbers are the same)
↑ 東寄りに道の網をたどった経路。
py -3.11 examples/poc_driving_endless_map.py
Source: examples/poc_driving_endless_map.py
This run produced 3 figures in total - see them all
Ops used (notes): pose_normalize · text_box · tile_digest · tile_edge_crossing · tile_height · tile_mesh · tile_params · tile_road_distance · tile_roads · tile_stream · world_camera
The author’s ideas: “there still aren’t nearly enough pedestrians and disturbances”, “bicycles ride on the road too”, “there are cars parked at the kerb”, “some people drive badly”, “not often, but some run out or cut across”, “with a parked car you have to time passing the oncoming traffic”, “while a bus is stopped people get on and off and may run out, so take care overtaking — better to wait until it leaves”, “if someone is waiting on the pavement, stop”, and “re-read the driving textbook and reproduce what is in it”. From the National Public Safety Commission notice Rules of the Road, 159 driver scenes were listed in a ledger with their reproduction status (docs/drive/kyosoku_scenarios.json: 8 reproduced, 11 partial, 7 not reproducible with reasons, 133 not started), and a gate makes the ledger and the PoCs name each other. New module drivetraffic, 18 ops. Gates: where a hidden point behind a parked car first becomes visible = the closed form of the sight line grazing the box corner = a sight-line test stepping the eye 2 mm at a time; at the fastest stoppable speed 10.7 km/h all 445 trials stop short of the child and 1.3× fails some / a child on the pavement goes visible → hidden → visible / the passing threshold in closed form = bisection on PET 1 s (1.6e-9 relative), mean wait 9.0 s at 600 oncoming cars/h = Adams’ formula / at the bus stop, waiting < passing slowly < passing at speed (closed-form expected count = space-time Poisson MC) / IDM platoons: careful and normal settle at the equilibrium gap, sloppy (reaction longer than headway) crashes at t = 10.8 s / reading lateral wobble by OU maximum likelihood finds 17 / 17 sloppy drivers with no false alarm (position spread: 13 / 17) / no crossing pedestrian missed / rare run-outs: mean = variance = ∫λ, importance sampling agrees with the closed form and naive MC at 1/25.7 the variance / 1.5 m cyclist clearance (a value chosen here) kept in every trial / background subtraction detects 0.133 s after the truth with no earlier false alarm / a 335 m run with no contact. Found on the way: position spread misses one bad driver in four; 2D sight lines are 0.5 s later than the 3D render (safe side); a stopped IDM platoon does not rest at exactly s0; passing slowly doubles the wait from 9.0 to 19.3 s. Honestly: habits, walking speed 1.2 m/s and the clearance are assumptions; blind spots are 2D. 20 gates, 68 s.
↑ Moving Traffic and Blind Spots — a Child Behind a Parked Car, Meeting Oncoming Traffic, a Bus Stop, Bad Drivers ―― The author’s ideas: “there still aren’t nearly enough pedestrians and disturbances”, “bicycles ride on the road too”, “there are cars parked at the kerb”, “some people drive badly”, “not often, but some run out or cut across”, “with a parked car you have to time passing the oncoming traffic”, “while a bus is stopped people get on and off and may run out, so take care overtaking — better to wait until it leaves”, “if someone is waiting on the pavement, stop”, and “re-read the driving textbook and reproduce what is in it”. From the National Public Safety Commission notice Rules of the Road, 159 driver scenes were listed in a ledger with their reproduction status (docs/drive/kyosoku_scenarios.json: 8 reproduced, 11 partial, 7 not reproducible with reasons, 133 not started), and a gate makes the ledger and the PoCs name each other. New module drivetraffic, 18 ops. Gates: where a hidden point behind a parked car first becomes visible = the closed form of the sight line grazing the box corner = a sight-line test stepping the eye 2 mm at a time; at the fastest stoppable speed 10.7 km/h all 445 trials stop short of the child and 1.3× fails some / a child on the pavement goes visible → hidden → visible / the passing threshold in closed form = bisection on PET 1 s (1.6e-9 relative), mean wait 9.0 s at 600 oncoming cars/h = Adams’ formula / at the bus stop, waiting < passing slowly < passing at speed (closed-form expected count = space-time Poisson MC) / IDM platoons: careful and normal settle at the equilibrium gap, sloppy (reaction longer than headway) crashes at t = 10.8 s / reading lateral wobble by OU maximum likelihood finds 17 / 17 sloppy drivers with no false alarm (position spread: 13 / 17) / no crossing pedestrian missed / rare run-outs: mean = variance = ∫λ, importance sampling agrees with the closed form and naive MC at 1/25.7 the variance / 1.5 m cyclist clearance (a value chosen here) kept in every trial / background subtraction detects 0.133 s after the truth with no earlier false alarm / a 335 m run with no contact. Found on the way: position spread misses one bad driver in four; 2D sight lines are 0.5 s later than the 3D render (safe side); a stopped IDM platoon does not rest at exactly s0; passing slowly doubles the wait from 9.0 to 19.3 s. Honestly: habits, walking speed 1.2 m/s and the clearance are assumptions; blind spots are 2D. 20 gates, 68 s.
↑ The measurement ―― 俯瞰(通し走行 86 s・335 m): 自車(青)は路肩駐車の死角の手前で 10.7 km/h に落とし、対向の車列(橙 = 荒い運転、横にふらつく)が過ぎて隙間 D が空くまで待ってからはみ出す。陰から走り出た子ども(黄)に止まり、渡り切ってから発進。脇道から出た自転車(紫)の後ろにつき、対向車が過ぎたら側方 1.5 m + 追い越しの間にふらつける幅 4.5 σ̂ √h を空けて追い越す。停車中のバス(緑)の前を乗客(赤)が渡るので追い越さずに発車まで待つ。紫の影は自車の目から見えない所。最小の距離: 子ども 3.07 m・自転車 2.87 m・対向車 0.78 m。 (figure labels are in Japanese; the numbers are the same)*
↑ 時空図(縦 = 先頭車からの位置、横 = 時刻、線 = 6 台)。
↑ 横断の意図の混同行列(480 人、判断の時刻は乱数、位置の雑音 5 cm・向き 10°)。
↑ 対向車がポアソン流のとき、はみ出しに要る隙間 τ = D/v_on を待つ平均時間(Adams の式)。*
↑ 停車中のバスの前後で出現率が高い(bus_stop_rate)。
py -3.11 examples/poc_driving_traffic.py
Source: examples/poc_driving_traffic.py
This run produced 8 figures in total - see them all
Ops used (notes): add_mesh_object · axes_frame · axes_transform · bus_stop_rate · driver_style · grid_lines · idm_accel · idm_equilibrium_gap · idm_platoon_simulate · importance_risk_estimate · lateral_wobble · long_params · long_simulate · nice_ticks · occlusion_reveal_distance · occlusion_safe_speed · occlusion_visible_intervals · ou_estimate · passing_decision · passing_gap_required · passing_simulate · pedestrian_crossing · pedestrian_mesh · plot_series … (+5)
The author’s ideas: “for signals you also need to predict from the pedestrian light”, “you need to check the mirrors”, “you have to do the right thing when an ambulance comes”, “while a bus is stopped … better to wait until it leaves”. Scenes on signal timing, emergency vehicles, amber lights and buses pulling out were taken from last part’s Rules-of-the-Road ledger. New module drivedecide, 18 ops. Gates: landmarks drawn through a virtual camera behind the mirror land on the ray-traced Fermat point (12 points, max 0.50 px; without the left-right flip, a median 228 px off) / the convex door mirror’s blind-spot polygon disagrees with none of 295 rendered posts, and R 1.4 m cuts a same-width flat mirror’s 37.1 m² to 20.4 m² / checking the rear left in the mirror before a left turn, no cyclist is hit in any of 400 trials (closest 1.08 m); with direct view only, 68 trials hit / the check order (mirror → signal about 3 s or 30 m before → change course → cancel) is scored with the driving-test deductions (NPA 丁運発第44号): our car loses 0 and each of 6 broken versions loses its points / the pedestrian lamp 200 m ahead is read from pixels in every frame, and every amber prediction interval contains the truth / over 600 trials the predicting car is never in the dilemma zone at amber, the non-predicting one 48 times under GHM (10 under the stop-line reading) / siren approaching/receding matches the sign of the range rate 99.89 %, and the bearing from the time difference is within 0.11° of geometry / the flashing light aliases to 1.25 Hz at 3.75 fps / for the ambulance the car pulls left and stops short of the junction (art. 40(1)); versions that do not pull left or stop inside the junction fail / the default is to wait for the bus to leave (closest gap 3.18 m); at 40 m the deceleration needed, 2.2957 m/s², equals a 0.1 ms braking simulation with bisection. Found on the way: the dilemma zone changes shape between GHM and the stop-line reading (no dilemma below 43 km/h for the latter); sound alone cannot tell front from back, so the flashing light in the mirror decides ‘behind’. Honestly: 1.0 s decision delay, 30 m ‘near a junction’, 1.0 m ‘pulled left’ and the mirror dimensions are assumptions; the siren’s 960 / 770 Hz and the 3 s amber are unconfirmed. The ledger now reads 15 reproduced, 12 partial, 7 not reproducible, 125 not started. 19 gates, 56.7 s.
↑ Decision Scenes — Mirrors, Predicting Amber from the Pedestrian Light, Giving Way to an Ambulance, Waiting for a Bus ―― The author’s ideas: “for signals you also need to predict from the pedestrian light”, “you need to check the mirrors”, “you have to do the right thing when an ambulance comes”, “while a bus is stopped … better to wait until it leaves”. Scenes on signal timing, emergency vehicles, amber lights and buses pulling out were taken from last part’s Rules-of-the-Road ledger. New module drivedecide, 18 ops. Gates: landmarks drawn through a virtual camera behind the mirror land on the ray-traced Fermat point (12 points, max 0.50 px; without the left-right flip, a median 228 px off) / the convex door mirror’s blind-spot polygon disagrees with none of 295 rendered posts, and R 1.4 m cuts a same-width flat mirror’s 37.1 m² to 20.4 m² / checking the rear left in the mirror before a left turn, no cyclist is hit in any of 400 trials (closest 1.08 m); with direct view only, 68 trials hit / the check order (mirror → signal about 3 s or 30 m before → change course → cancel) is scored with the driving-test deductions (NPA 丁運発第44号): our car loses 0 and each of 6 broken versions loses its points / the pedestrian lamp 200 m ahead is read from pixels in every frame, and every amber prediction interval contains the truth / over 600 trials the predicting car is never in the dilemma zone at amber, the non-predicting one 48 times under GHM (10 under the stop-line reading) / siren approaching/receding matches the sign of the range rate 99.89 %, and the bearing from the time difference is within 0.11° of geometry / the flashing light aliases to 1.25 Hz at 3.75 fps / for the ambulance the car pulls left and stops short of the junction (art. 40(1)); versions that do not pull left or stop inside the junction fail / the default is to wait for the bus to leave (closest gap 3.18 m); at 40 m the deceleration needed, 2.2957 m/s², equals a 0.1 ms braking simulation with bisection. Found on the way: the dilemma zone changes shape between GHM and the stop-line reading (no dilemma below 43 km/h for the latter); sound alone cannot tell front from back, so the flashing light in the mirror decides ‘behind’. Honestly: 1.0 s decision delay, 30 m ‘near a junction’, 1.0 m ‘pulled left’ and the mirror dimensions are assumptions; the siren’s 960 / 770 Hz and the 3 s amber are unconfirmed. The ledger now reads 15 reproduced, 12 partial, 7 not reproducible, 125 not started. 19 gates, 56.7 s.
↑ The measurement ―― 信号の予測(車載 640 × 360・10 fps + 俯瞰の帯)。並行する歩行者信号(右上に灯火のまわりを同じ焦点距離で描いた拡大)の青が t = 20.50 s に初めて消えたコマで「青点滅」と分かり、青点滅の長さ F = 10 s と Δ = 2 s から車両の黄を 32.23 s(±0.27、真値 32 s)と予測。50 km/h の自車(青)はそのままだと黄の瞬間に GHM のジレンマゾーン(50 km/h で橙、17.2〜46.0 m)に入ると分かるので、早めに 2 m/s² で減速して停止線で止まる。予測しない自車(黒)は黄の瞬間に停止線の 33.3 m 手前 = ジレンマゾーンの中。下の帯は行ごとに「その車の今の速さとブレーキの状態」でのジレンマゾーン(門の in_dilemma と同じ式、減速中は反応 0。上 = GHM 橙、下 = 停止線の読み 赤、50 km/h では 41.7〜46.0 m)—— 減速している青の車の帯は縮み、車は帯の外にいる。 (figure labels are in Japanese; the numbers are the same)
↑ 黄が点いた瞬間の (速さ, 停止線までの距離) の平面。
↑ 合成したサイレン(960 / 770 Hz を 0.65 s ずつ、発音時刻の 2 次方程式だけで作る)を左のマイクで聞いたスペクトログラム。
↑ 左後方(車の中心から左 1.2〜4.6 m・目の後ろ 15 m まで)のうち、左ドアミラーにも直接の視界(方位 100° まで、青の線)にも入らない所(赤)。
↑ 技能試験の採点基準(警察庁 丁運発第44号: 安全不確認 10 点・合図不履行等 5 点)を check_sequence_score で。
py -3.11 examples/poc_driving_decisions.py
Source: examples/poc_driving_decisions.py
This run produced 6 figures in total - see them all
Ops used (notes): add_mesh_object · aliased_frequency · axes_frame · axes_transform · bus_departure_yield_check · check_sequence_score · convex_mirror_fov · dilemma_zone · doppler_shift · doppler_track · filled_polygon · flash_frequency · grid_lines · idm_accel · legend_box · mirror_aim_normal · mirror_blind_zone · mirror_virtual_camera · nice_ticks · plot_series · predict_amber_onset · signal_phase_plan · signal_state · siren_signal … (+6)
The author’s policies: “let the AI work out combinations of parts, and add parts only if they are rule-based” and “if you put random values in, do not accept ones that are statistically far too extreme”. From the Rules-of-the-Road ledger: keep left, do not straddle lane lines, do not run onto the shoulder, slow down near corners, turn left close to the left edge along the kerb, turn right just inside the centre. New module drivelateral, 20 ops. Gates: a sample gate (two-sided 0.1 % points one by one with a recorded reason, KS for the population) drops 22 of 24,000 cyclist speeds with KS p = 0.905, and a unit error that passes the one-by-one gate 86 % of the time is caught by KS at D = 0.986 / a clothoid with R 100 m and L 40 m meets the Road Structure Ordinance tables and the manual’s 0.5–0.75 m/s³ / 120 planned drivers stay inside the friction circle (max usage 0.48) and under 10 km/h near the corner; without a plan everyone leaves it (usage 1.65–2.42) / nobody straddles the centre line (closest 0.24 m) or runs onto the shoulder (closest 0.34 m) / pure pursuit’s steady offset = the 2-DOF formula to 0.0001 m / the off-tracking closed form = the numerical rear-wheel path to 0.2 mm, 0.68 m at 90° / the keep-left turn has no violation and passes the waiting cyclist by 0.66 m, while tracing the corner with the front wheel touches it / a left turn without keeping left leaves a chance of catching a following cyclist, with importance sampling p = 0.00024 consistent with naive MC (12 / 24000) by a Poisson test / wide and early right turns are violations. Found on the way: the kinematic formula gives 41–47 % of the steady offset; off-tracking does not reach steady state in a 90° turn; a one-by-one gate lets unit errors through; rare events are weighted, not dropped. Honestly: linear tyres with no saturation; dimensions, verdict widths and the reference distributions for μ and the rest are assumptions (only the mean cyclist speed and the ordinance tables are primary). The ledger now reads 22 reproduced, 12 partial, 7 not reproducible, 118 not started. 15 gates, 45.1 s.
↑ Lateral Motion — Slowing Before a Bend, Staying in the Lane, Keeping Left to Turn Left, Off-Tracking ―― The author’s policies: “let the AI work out combinations of parts, and add parts only if they are rule-based” and “if you put random values in, do not accept ones that are statistically far too extreme”. From the Rules-of-the-Road ledger: keep left, do not straddle lane lines, do not run onto the shoulder, slow down near corners, turn left close to the left edge along the kerb, turn right just inside the centre. New module drivelateral, 20 ops. Gates: a sample gate (two-sided 0.1 % points one by one with a recorded reason, KS for the population) drops 22 of 24,000 cyclist speeds with KS p = 0.905, and a unit error that passes the one-by-one gate 86 % of the time is caught by KS at D = 0.986 / a clothoid with R 100 m and L 40 m meets the Road Structure Ordinance tables and the manual’s 0.5–0.75 m/s³ / 120 planned drivers stay inside the friction circle (max usage 0.48) and under 10 km/h near the corner; without a plan everyone leaves it (usage 1.65–2.42) / nobody straddles the centre line (closest 0.24 m) or runs onto the shoulder (closest 0.34 m) / pure pursuit’s steady offset = the 2-DOF formula to 0.0001 m / the off-tracking closed form = the numerical rear-wheel path to 0.2 mm, 0.68 m at 90° / the keep-left turn has no violation and passes the waiting cyclist by 0.66 m, while tracing the corner with the front wheel touches it / a left turn without keeping left leaves a chance of catching a following cyclist, with importance sampling p = 0.00024 consistent with naive MC (12 / 24000) by a Poisson test / wide and early right turns are violations. Found on the way: the kinematic formula gives 41–47 % of the steady offset; off-tracking does not reach steady state in a 90° turn; a one-by-one gate lets unit errors through; rare events are weighted, not dropped. Honestly: linear tyres with no saturation; dimensions, verdict widths and the reference distributions for μ and the rest are assumptions (only the mean cyclist speed and the ordinance tables are primary). The ledger now reads 22 reproduced, 12 partial, 7 not reproducible, 118 not started. 15 gates, 45.1 s.
↑ The measurement ―― 主図(車載カメラ 640 × 360、356 コマ)。場面 1(190 コマ): 半径 100 m のカーブと半径 15 m の曲がり角(道路構造令の設計速度 50・20 km/h の表の値、クロソイドでつなぐ)。curvature_speed_plan の計画どおりカーブの手前で減速し、曲がり角の附近は 10 km/h、pure pursuit(注視距離は 2 自由度の定常の横ずれ ≤ 0.25 m で上限)で車線の中を走る。右上 = 摩擦円(黒 = μg、青 = この人の横加速度の上限、赤 = 今の加速度)。場面 2: 外側線から 0.25 m に寄って左折、後ろから 23 km/h の自転車は左に入れない(巻き込み なし)。場面 3: 車線の中央のまま左折、同じ自転車(同じ距離・速さ)が左に入り込み、曲がる車体に触れる(巻き込み あり)。右上 = 上から見た図(青の線 = 後輪の内側の軌跡、橙 = 隅で人・自転車が待つ所)。 (figure labels are in Japanese; the numbers are the same)
↑ 摩擦円: 車の加速度を μg で割った点(横 = 横加速度(左旋回を左に描く)、縦 = 縦加速度、黒の円 = 使用率 1)。
↑ 曲率から作る速度計画(前向き・後ろ向きの 2 パス、摩擦円つき)。
↑ 稀な出来事: 車線の中央のまま左折すると、後ろの自転車が左に入り込んで巻き込まれるのは、速い自転車(22.1 km/h 以上、採った速さの 2.9 %)がちょうど曲がる頃に追いつく細い帯だけ。
↑ 乱数の値の門: 自転車の速さは参照分布 N(14.5, 3.5) km/h(平均は国総研の車道の旅行速度、幅は仮定)の両側 0.1 % 点(3.0〜26.0 km/h)の外を 1 個ずつ落としてから採り(24000 個、KS p = 0.91)、残りを切断正規との KS で照合する。
↑ The animation ―― 上から見た動画(229 コマ)。同じ運転者(μ 0.72)を、curvature_speed_plan の計画どおり(上左、青)と 50 km/h のまま(上右、赤)で走らせ、同じ道のりの所を並べる(60 m 四方、車について動く。点 = 過去 12 s の通った跡、青 = 摩擦円の中、赤 = 外)。下左 = 速さ(青の帯 = 120 人の計画の最小〜最大、青の線 = この人、赤 = 計画なし、橙 = 曲がり角の附近の徐行の区間、黒の縦線 = 今)、下右 = 摩擦円(加速度 / μg、横 = 横加速度(左旋回で左)・縦 = 縦加速度)。計画なしの点は曲がり角で円の外へ出る(使用率 最大 2.15 = 線形モデルの外、実際には滑る)。
py -3.11 examples/poc_driving_lateral.py
Source: examples/poc_driving_lateral.py
This run produced 8 figures in total - see them all
Ops used (notes): ackermann_steer_angles · add_mesh_object · bicycle_model_step · clothoid_design · clothoid_points · curvature_speed_plan · ellipse · friction_circle_usage · lateral_offset · offtracking_circle · pure_pursuit_circle_offset · pure_pursuit_curvature · rear_axle_path · text_box · time_to_line_crossing · turn_maneuver_check · understeer_gradient · world_camera · world_move
The author’s ideas: “let several kinds of humanoid walk in the driving world in place of people”, “collisions need not be precise”, “draw each object in advance with a mask”. From seven real humanoid robots in MuJoCo Menagerie, one procedural gait cycle is built with joint roles taken from the body’s shape rather than its names, and the look is made cheap in two ways. New module drivehumanoid (4 ledger ops + fs.humanoid_walk_clip). Gates: grounded every frame and no stance-foot slip / grid-decimated mesh shifts ≤ √3·cell (humanoid 1: 393,270 → 1,497 triangles, cell 5.5 cm, largest shift 6.4 cm) / masked impostors drawn from 16 directions × 12 phases match direct rendering (IoU 0.984–1.000 on the steps), and a wall in front takes 612 pixels to 0. Ten bodies cost 1.02× the ground alone (impostors), 1.23× (decimated) and 4.50× (30,000 triangles each). Honestly: the gait is a periodic formula without balance, impostors are far horizontal pictures scaled down, decimation drops humanoid 1’s neck so the head floats (IoU 0.77), and two more models are left out.
↑ Humanoids on the Crosswalk — Keep the Car’s Physics Exact, Draw the Walkers Cheaply ―― The author’s ideas: “let several kinds of humanoid walk in the driving world in place of people”, “collisions need not be precise”, “draw each object in advance with a mask”. From seven real humanoid robots in MuJoCo Menagerie, one procedural gait cycle is built with joint roles taken from the body’s shape rather than its names, and the look is made cheap in two ways. New module drivehumanoid (4 ledger ops + fs.humanoid_walk_clip). Gates: grounded every frame and no stance-foot slip / grid-decimated mesh shifts ≤ √3·cell (humanoid 1: 393,270 → 1,497 triangles, cell 5.5 cm, largest shift 6.4 cm) / masked impostors drawn from 16 directions × 12 phases match direct rendering (IoU 0.984–1.000 on the steps), and a wall in front takes 612 pixels to 0. Ten bodies cost 1.02× the ground alone (impostors), 1.23× (decimated) and 4.50× (30,000 triangles each). Honestly: the gait is a periodic formula without balance, impostors are far horizontal pictures scaled down, decimation drops humanoid 1’s neck so the head floats (IoU 0.77), and two more models are left out.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ 同じ場面の 1 コマ
↑ The animation ―― 横断歩道を渡る 7 機種(マスク付きの事前描画、1 体 数 ms)
py -3.11 examples/poc_driving_humanoids.py
Source: examples/poc_driving_humanoids.py
This run produced 4 figures in total - see them all
Ops used (notes): add_mesh_object · crosswalk_mesh · humanoid_clip_mesh · humanoid_impostors · pedestrian_mesh · world_camera · world_camera_impostors
From the not-yet-started part of the Rules-of-the-Road ledger, scenes that rule-based parts can reproduce — level crossings (stop just before and look both ways, never enter during the alarm or with the far side blocked, keep slightly to the middle), right of way (slow down and give way to a wider road, give way to the left), pedestrian crossings (stop beside a stopped car, no overtaking within 30 m) and no-stopping distances. New module drivecrossing, 17 ops; random values pass the sample gate (0.1 % points and KS). Gates: the railway interpretation standard’s timing (with barriers: alarm to closed 15 s, closed to arrival 20 s) is met, and the speed at which a fixed start point breaks the minimum is 152 km/h in closed form / the 240 rule-following drivers have no violation, all cross, and nobody is on the track when a train arrives (smallest margin 31.5 s); not stopping no_stop 79, entering during the alarm 157 (matching the state machine; 23 of them on the track) / the sight-triangle closed form 19.03 m = ray brute force, and the probability of an unseen train (building 7.92e-03, no re-check 2.11e-02) agrees between closed form, MC and importance sampling within 1.1σ / joining a wider road never makes the crossing car slow down, while naive driving obstructs in 104 / 240; give-way-to-the-left includes scenes where a car from the right gives way / stop beside a car stopped at a crossing, no pulling ahead of a car within 30 m (cyclists exempt) / art. 44 zones = painted directly on a 1 mm grid; stopping where one wants violates 476 / 1000 (all six kinds) / the alternating lamps read from pixels at 0.833 Hz with phase difference π, and decimation aliases to 0.500 Hz as the formula says. Found on the way: alarm-to-arrival 30 s is the value for crossings without barriers; a fixed start point gives slower trains longer alarms; the alarm can start just after moving off (a dilemma); dirt that slipped past the gates (the train hiding the lamp) was found by looking. Honestly: width ratio 1.5, ‘sudden’ 2.0 m/s², barrier times and reference distributions are assumptions; 50 flashes per minute is from a secondary source; listening is not modelled. The ledger now reads 33 reproduced, 14 partial, 7 not reproducible, 105 not started. 22 gates, 18.7 s.
↑ Level Crossings and Right of Way — Stop and Look, Never Enter During the Alarm or Into a Blocked Exit, Yield to the Wider Road ―― From the not-yet-started part of the Rules-of-the-Road ledger, scenes that rule-based parts can reproduce — level crossings (stop just before and look both ways, never enter during the alarm or with the far side blocked, keep slightly to the middle), right of way (slow down and give way to a wider road, give way to the left), pedestrian crossings (stop beside a stopped car, no overtaking within 30 m) and no-stopping distances. New module drivecrossing, 17 ops; random values pass the sample gate (0.1 % points and KS). Gates: the railway interpretation standard’s timing (with barriers: alarm to closed 15 s, closed to arrival 20 s) is met, and the speed at which a fixed start point breaks the minimum is 152 km/h in closed form / the 240 rule-following drivers have no violation, all cross, and nobody is on the track when a train arrives (smallest margin 31.5 s); not stopping no_stop 79, entering during the alarm 157 (matching the state machine; 23 of them on the track) / the sight-triangle closed form 19.03 m = ray brute force, and the probability of an unseen train (building 7.92e-03, no re-check 2.11e-02) agrees between closed form, MC and importance sampling within 1.1σ / joining a wider road never makes the crossing car slow down, while naive driving obstructs in 104 / 240; give-way-to-the-left includes scenes where a car from the right gives way / stop beside a car stopped at a crossing, no pulling ahead of a car within 30 m (cyclists exempt) / art. 44 zones = painted directly on a 1 mm grid; stopping where one wants violates 476 / 1000 (all six kinds) / the alternating lamps read from pixels at 0.833 Hz with phase difference π, and decimation aliases to 0.500 Hz as the formula says. Found on the way: alarm-to-arrival 30 s is the value for crossings without barriers; a fixed start point gives slower trains longer alarms; the alarm can start just after moving off (a dilemma); dirt that slipped past the gates (the train hiding the lamp) was found by looking. Honestly: width ratio 1.5, ‘sudden’ 2.0 m/s², barrier times and reference distributions are assumptions; 50 flashes per minute is from a secondary source; listening is not modelled. The ledger now reads 33 reproduced, 14 partial, 7 not reproducible, 105 not started. 22 gates, 18.7 s.
↑ The measurement ―― 車載カメラ(640 × 360、114 コマ)。片側 2 車線、左の車線の SUV が横断歩道の直前で止まっている(歩行者 1.78 m/s が陰から渡る)。場面 1(72 コマ): 横に並ぶ前に一時停止、徐行で横断歩道の直前へ出て、歩行者が見えて渡り終えるまで待つ —— crosswalk_stopped_vehicle_check = 違反なし、触れた = いいえ。場面 2(42 コマ): 40 km/h のまま横を抜け、見えてから 0.75 s 後に 6 m/s² で止まろうとする —— 判定 = 38 条 2 項の違反、触れた = はい。 (figure labels are in Japanese; the numbers are the same)
↑ 主図の場面 1 の時刻。
↑ 主図の車載カメラのコマ(4.0 fps、運転者が前を向いて止まっている間、列車が灯を隠す前まで)で、向こう側の柱の 2 灯の画素を読んだ時系列。
↑ 見える距離 19.0 m(sight_triangle_distance = 光線の総当たり)。
↑ 同じ 240 人(列車の速さ・発進・見る時間は標本の門を通った値)を 5 通りの方針で。
↑ The animation ―― 上から見た動画(151 コマ、0.5 秒ごと)。乱数の場面 #116(列車 96 km/h、向こう側の列の後端 = 踏切の端から 3.6 m)を 3 人で。左 = ルール(違反なし)、中 = 向こう側が詰まっていても入る(no_exit_room, stopped_inside)、右 = 警報中でも入る(entered_while_forbidden、列車が着くとき線路の上 = いいえ)。赤い車 = 警報〜上昇中に車体が踏切の上。
↑ The animation ―― 上から見た動画(85 コマ)。狭い道(4 m)から広い道(7.0 m)へ(S098、36 条 2・3 項)。場面 #0 の同じ交差道路の車で、左 = ルール(線で徐行 10 km/h、7.9 s 待ってから、交差道路の車に減速を一切させない時刻に発進)、右 = 30 km/h のまま入る(交差道路の車に要った減速度 最大 5.1 m/s² = 進行妨害)。
py -3.11 examples/poc_driving_crossing.py
Source: examples/poc_driving_crossing.py
This run produced 11 figures in total - see them all
Ops used (notes): add_mesh_object · aliased_frequency · conflict_zone_intervals · crossing_clear_time · crossing_gate_state · crossing_lamp_signal · crossing_stop_check · crossing_timing_check · crosswalk_mesh · crosswalk_overtake_check · crosswalk_stopped_vehicle_check · ellipse · exit_room_check · flash_frequency · intersection · lamp_pair_phase · legal_stop_intervals · no_stopping_zones · obstruction_decel · parking_position_check · pedestrian_mesh · priority_rule · sight_triangle_distance · text_box … (+3)
From the not-yet-started part of the Rules-of-the-Road ledger, overtaking and what-you-cannot-see scenes that rule-based parts can reproduce — overtaking (wait while sight distance is short of the required D*, art. 30 no-overtaking zones, return once the whole lead car shows in the rear-view mirror), the overtaken car does not speed up, a lane change must not make the follower brake hard, roundabouts (enter slowly, signal left after passing the exit before yours), sight distance over a crest, and curve mirrors. New module drivepass, 17 ops; random values pass the sample gate (0.1 % points and KS). Gates: rear-view-mirror gap closed form 19.75 m = Fermat shortest-path scan / art. 30 zones = brute force over the text / 188 of the 240 rule-following drivers overtake, 0 in zones, smallest PET with oncoming cars 5.62 s; naive: 18 in zones, 44 with PET < 2 s, 240 / 240 cut-ins / the follower’s required deceleration in closed form = two-car time march / the ring car’s required deceleration = time march along the ring, and the left signal starts exactly when the replay passes the exit; naive obstructs 38 / 240 / three rows of the Road Structure Ordinance’s sight-distance table reproduced / distance read from a convex mirror’s image size, k·a = exact 3-D ray tracing (30 m → 190.04 m); at 45° the readings split as Coddington says, k_s = 4.75 vertically and k_t = 8.58 horizontally; near blind stretch 8.16 m. Found on the way: a convex mirror makes cars look farther, but faster or slower depending on how speed is read / a 30 m crest ‘vicinity’ covers only 60 m of the 522 m where sight distance is short / a naive cut-in needs no hard braking from the lead car (the danger shows in headway and PET). Honestly: ‘vicinity’ 30 m, ‘steep’ 10 %, ‘sudden’ 2.0 m/s², crawling 10 km/h, mirror dimensions and reference distributions are assumptions; enforcement order art. 21 was not checked. The ledger now reads 38 reproduced, 17 partial, 7 not reproducible, 97 not started. 24 gates, 3.5 s.
↑ Overtaking and What You Cannot See — Wait for Sight Distance, Return Once in the Mirror, Do Not Obstruct the Ring, and Why Curve Mirrors Make Cars Look Far ―― From the not-yet-started part of the Rules-of-the-Road ledger, overtaking and what-you-cannot-see scenes that rule-based parts can reproduce — overtaking (wait while sight distance is short of the required D, art. 30 no-overtaking zones, return once the whole lead car shows in the rear-view mirror), the overtaken car does not speed up, a lane change must not make the follower brake hard, roundabouts (enter slowly, signal left after passing the exit before yours), sight distance over a crest, and curve mirrors. New module drivepass, 17 ops; random values pass the sample gate (0.1 % points and KS). Gates: rear-view-mirror gap closed form 19.75 m = Fermat shortest-path scan / art. 30 zones = brute force over the text / 188 of the 240 rule-following drivers overtake, 0 in zones, smallest PET with oncoming cars 5.62 s; naive: 18 in zones, 44 with PET < 2 s, 240 / 240 cut-ins / the follower’s required deceleration in closed form = two-car time march / the ring car’s required deceleration = time march along the ring, and the left signal starts exactly when the replay passes the exit; naive obstructs 38 / 240 / three rows of the Road Structure Ordinance’s sight-distance table reproduced / distance read from a convex mirror’s image size, k·a = exact 3-D ray tracing (30 m → 190.04 m); at 45° the readings split as Coddington says, k_s = 4.75 vertically and k_t = 8.58 horizontally; near blind stretch 8.16 m. Found on the way: a convex mirror makes cars look farther, but faster or slower depending on how speed is read / a 30 m crest ‘vicinity’ covers only 60 m of the 522 m where sight distance is short / a naive cut-in needs no hard braking from the lead car (the danger shows in headway and PET). Honestly: ‘vicinity’ 30 m, ‘steep’ 10 %, ‘sudden’ 2.0 m/s², crawling 10 km/h, mirror dimensions and reference distributions are assumptions; enforcement order art. 21 was not checked. The ledger now reads 38 reproduced, 17 partial, 7 not reproducible, 97 not started. 24 gates, 3.5 s.*
↑ The measurement ―― 上から見た動画(178 コマ、0.2 s ごと = 実時間)。環状交差点(環道の半径 14 m、右回り)に南から入り 出口 3(右折) へ出る場面 #0 を、同じ環道の車で。左 = 規則: 環道の車に 2.0 m/s² を超える減速を要らせない時刻まで 0.8 s 待ち、徐行(10 km/h)で入り、出口の 1 つ手前の出口の側方で左の合図(roundabout_signal_point、黄の点)—— roundabout_signal_check = 違反なし。右 = 素朴: 20 km/h のまま入り(環道の車に要らせた減速度 最大 3.1 m/s² = 37 条の 2 第 1 項の進行妨害)、右の合図で入り、出口の直前で左に変える(right_signal, left_late)。 (figure labels are in Japanese; the numbers are the same)
↑ 参照 N(55, 6) km/h(仮定)。
↑ 凸形縦断曲線(±4 %、L 160 m、R 2000 m)の道で、運転者の目(1.2 m)から対向車(1.2 m)が見える距離。
↑ 平面鏡のつもりで像の大きさから距離を読むと k 倍遠い。
↑ 240 場面(後続車の速さ・反応は標本の門を通った値、自車 50 km/h、残す車間 2 m)。
↑ The animation ―― 目玉の動画(51 コマ、実時間)。見通しの悪い T 字路(右の角は高さ 2 m の塀)で停止線に止まった運転者が、向こう側のカーブミラー(凸面 R 3 m・直径 0.8 m、目から 8.0 m、入射角 45°)を見る。左 = 運転席から見た鏡の拡大: 鏡の中は球面での反射の光線を環境の画像から引いて描いた(視差を深度で 3 回補正、車のメッシュの頂点の厳密な光線追跡と画素で照合)。右から 30 km/h で来る赤い車は、鏡から 29 m のとき鏡の中で高さ 48 px(同じ所の平面鏡なら 189 px)—— 画素の比で読むと 139 m 先。閉形式の読みは縦 140 m・横 253 m(Coddington の式 k_s = 4.75、k_t = 8.58。正面から見る近軸なら k = 6.33 で 187 m)。右上 = 運転席からの広い眺め、右下 = 上から見た本当の位置(赤の線 = 鏡に映らない手前 8.2 m、橙 = 映る範囲、緑 = 塀の陰から直接見える所。下の目盛り = 本当の距離と鏡の読み)。
py -3.11 examples/poc_driving_pass.py
Source: examples/poc_driving_pass.py
This run produced 9 figures in total - see them all
Ops used (notes): convex_mirror_image · convex_mirror_misjudge · crest_safe_speed · crest_sight_distance · ellipse · intersection · lane_change_follower_decel · lane_change_permitted · load_asset · mirror_aim_normal · mirror_road_coverage · no_overtaking_zones · overtake_permitted · overtake_requirement · overtake_return_gap · overtaken_conduct_check · roundabout_entry_check · roundabout_signal_check · roundabout_signal_point · text_box · world_camera
These runs hang in the mathematical-picture series, not in the metrology museum. The dividing line is where the ground truth comes from: a metrology exhibit measures an object and the truth lives in that object, while these runs have no object at all — the truth comes from a theorem, an identity or an invariant of the drawing itself. That article is written by hand, so the generator does not render this lane.
Hokusai’s Great Wave (Met Open Access, CC0) turned into one closed line and then redrawn as a chain of rotating circles. Stylisation always produces a plausible picture, so every stage is gated by a number instead. The stipple follows tone (0.9946 on a ramp, against an evenly spread control) — but correlation hides the exponent: a centroidal Voronoi tessellation puts density at sqrt(weight), not weight (Gersho), measured 0.61, rising to 0.77 with the weight squared. The tour is scored against the minimum spanning tree, which no closed tour can beat (1.142, against 41.3 for coordinate order). Tone correlation is 0.983 against +0.013 for random points. The pen width is solved in closed form and lands at 0.891 of the target ink — the missing 11 % is exactly the stroke overlap. Nyquist is checked before the Fourier step: equal-arc-length resampling cuts corners and shortens the stroke when the sample spacing is coarser than the segments (0.934 of the length at 16,384 samples, 0.984 at 65,536; the error falls as 1/N). Parseval then turns the number of circles into a prediction made before the drawing exists, and the GIF shows the real rotating arms: 1, 4, 16, 90, 600, 4000, with the picture giving itself away as they multiply. The same line leaves as G-code: 15.51 m of stroke, 8.6 minutes on a plotter. Run it on your own photograph with –image.
↑ A Photograph as One Line, Drawn by Rotating Circles — Tone, Stipple, Tour, Fourier Series, G-code ―― Hokusai’s Great Wave (Met Open Access, CC0) turned into one closed line and then redrawn as a chain of rotating circles. Stylisation always produces a plausible picture, so every stage is gated by a number instead. The stipple follows tone (0.9946 on a ramp, against an evenly spread control) — but correlation hides the exponent: a centroidal Voronoi tessellation puts density at sqrt(weight), not weight (Gersho), measured 0.61, rising to 0.77 with the weight squared. The tour is scored against the minimum spanning tree, which no closed tour can beat (1.142, against 41.3 for coordinate order). Tone correlation is 0.983 against +0.013 for random points. The pen width is solved in closed form and lands at 0.891 of the target ink — the missing 11 % is exactly the stroke overlap. Nyquist is checked before the Fourier step: equal-arc-length resampling cuts corners and shortens the stroke when the sample spacing is coarser than the segments (0.934 of the length at 16,384 samples, 0.984 at 65,536; the error falls as 1/N). Parseval then turns the number of circles into a prediction made before the drawing exists, and the GIF shows the real rotating arms: 1, 4, 16, 90, 600, 4000, with the picture giving itself away as they multiply. The same line leaves as G-code: 15.51 m of stroke, 8.6 minutes on a plotter. Run it on your own photograph with –image.
↑ The measurement ―― weighted Lloyd puts points where the picture is dark. The claim is measured, not looked at: on a ramp the count per band correlates 0.995 with darkness, and on a flat image the points spread evenly (nearest-neighbour cv 0.13 against 0.37 for the ramp) (figure labels are in Japanese; the numbers are the same)
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_one_stroke_epicycles/02_lloyd.png) |
| *↑ Lloyd’s energy sum w | x-c(x) | ^2 never goes up — that is a property of the algorithm that can be checked exactly, which is why it is the gate rather tha…* |
↑ both pictures visit exactly the same 9000 points exactly once; only the order differs.
↑ while the strokes do not overlap the ink fraction is length x width / area, so the width that reproduces the mean tone is solved in closed form (0.90…
↑ every row is measured by an operator in this run; the harmonic rows are predicted in closed form before the drawing is made
py -3.11 examples/poc_one_stroke_epicycles.py
Source: examples/poc_one_stroke_epicycles.py
This run produced 9 figures in total - see them all
Ops used (notes): contour_epicycle_chain · contour_fourier_complex · contour_fourier_truncation_energy · contours_to_gcode · gcode_time_estimate · mst_length · stipple_energy · stipple_points_from_image · stroke_resample_closed · stroke_tone_error · stroke_tour_closed
Complex analysis makes beautiful pictures, and beautiful pictures are the ones nobody checks. This exhibit draws four of them — domain colouring, Newton’s basins, escape time, and potential flow past an aerofoil — and scores each against a truth that is independent of the drawing. Zeros and poles are counted by this family’s existing cplx_winding_number (+3, +1, -2, with an out-of-window control that counts 0). The hue winding read from the rendered pixels alone recovers the order of a zero: the argument principle, visible. Cayley’s 1879 theorem gives the degree-2 basins in closed form as two half-planes, and all 262,144 pixels agree with zero unconverged; degree 3 is the case Cayley could not settle, and on the same grid the boundary grows from 1,026 to 13,348 pixels — yet the conjugate symmetry stays exact. The main cardioid and period-2 bulb prove interiority without iterating: 85,624 pixels, zero counterexamples, and honestly only 90.1 % of what actually stays. The flow field is holomorphic, so cplx_cr_residual reads 2.06e-04; circulation is path independent; the trailing edge stays finite only because the Kutta condition picked the circulation, and with zero circulation it diverges by more than 20x. The lift exceeds thin-aerofoil theory by exactly the thickness ratio a/b, to under 1e-9.
↑ Seeing the Complex Plane as an Area — When the Picture Proves the Theorem ―― Complex analysis makes beautiful pictures, and beautiful pictures are the ones nobody checks. This exhibit draws four of them — domain colouring, Newton’s basins, escape time, and potential flow past an aerofoil — and scores each against a truth that is independent of the drawing. Zeros and poles are counted by this family’s existing cplx_winding_number (+3, +1, -2, with an out-of-window control that counts 0). The hue winding read from the rendered pixels alone recovers the order of a zero: the argument principle, visible. Cayley’s 1879 theorem gives the degree-2 basins in closed form as two half-planes, and all 262,144 pixels agree with zero unconverged; degree 3 is the case Cayley could not settle, and on the same grid the boundary grows from 1,026 to 13,348 pixels — yet the conjugate symmetry stays exact. The main cardioid and period-2 bulb prove interiority without iterating: 85,624 pixels, zero counterexamples, and honestly only 90.1 % of what actually stays. The flow field is holomorphic, so cplx_cr_residual reads 2.06e-04; circulation is path independent; the trailing edge stays finite only because the Kutta condition picked the circulation, and with zero circulation it diverges by more than 20x. The lift exceeds thin-aerofoil theory by exactly the thickness ratio a/b, to under 1e-9.
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_complex_plane_fields/01_rational.png) |
| *↑ The measurement ―― 有理関数 (z−z₁)(z−z₂)/(z−p) の位相彩色。色相 = 偏角、明度 = | z | 。零点では黒く、極では明るく色相が逆に回る。既存 op の cplx_winding_number が同じ場から数えた巻き数は +1 (零点 2 − 極 1)で、絵の色相の回り方と一致する。 (figure labels are in Japanese; the numbers are the same)* |
↑ ★絵が定理を証明する側に回る。
↑ 2 次の境界は虚軸 1 本(513 画素の格子で 1026)。
↑ 迎角 8 度のジューコフスキー翼まわりの非粘性流。
↑ ★CL = 2π(a/b)sin(α+β)。
py -3.11 examples/poc_complex_plane_fields.py
Source: examples/poc_complex_plane_fields.py
This run produced 10 figures in total - see them all
Ops used (notes): boundary · cplx_cr_residual · cplx_domain_colour · cplx_escape_time · cplx_newton_basins · cplx_plane_grid · cplx_rational_field · cplx_winding_number · joukowski_circulation · mandelbrot_interior · potential_flow_joukowski
| Mathematically beautiful figures are never checked, because they look right: a slightly wrong implementation still packs circles and still spirals. So six of them are drawn and scored only against a truth that lives outside the picture. Descartes’ circle theorem: generation uses the reflection k’ = 2(k1+k2+k3) - k4, so tangency is re-discovered from distances before being fed to the theorem — over the 164 circles of depth 4, every tangent quadruple agrees to 1e-13. An integral seed (-1, 2, 2, 3) keeps integer curvatures for ever (Lagarias-Mallows-Wilks), catching an error a picture can never show. Ford circles are tangent exactly when | ps - qr | = 1: zero disagreements over 1,081 pairs, and the count matches 1 + sum of Euler’s totient. A geodesic dome has exactly 12 degree-5 vertices at every subdivision (V - E + F = 2) — and the degrees are counted by an existing, independent op (graph_degree_table), because counting them again yourself proves nothing. Phyllotactic spirals look like spirals at any angle, so the neighbour index gaps are counted instead: 7 of 7 peaks are Fibonacci at the golden angle, 2 of 7 for the controls. Moran’s equation solves the dimension before anything is drawn (1.5850 = log3/log2 for Sierpinski) while the existing box-counting op reads 1.6164 off the points — different derivations, different inputs. Non-similarity maps (Barnsley’s fern) are refused. Space-filling curves visit each of 4^n cells once with unit steps, and locality is reported as a table, not a slogan (Hilbert 6.38 at k=32 against sqrt(32) = 5.66; raster 16.07). |
| *↑ Theorems as Gates — Apollonian Gaskets, Ford Circles, Geodesic Domes, Phyllotaxis, IFS, Space-Filling Curves ―― Mathematically beautiful figures are never checked, because they look right: a slightly wrong implementation still packs circles and still spirals. So six of them are drawn and scored only against a truth that lives outside the picture. Descartes’ circle theorem: generation uses the reflection k’ = 2(k1+k2+k3) - k4, so tangency is re-discovered from distances before being fed to the theorem — over the 164 circles of depth 4, every tangent quadruple agrees to 1e-13. An integral seed (-1, 2, 2, 3) keeps integer curvatures for ever (Lagarias-Mallows-Wilks), catching an error a picture can never show. Ford circles are tangent exactly when | ps - qr | = 1: zero disagreements over 1,081 pairs, and the count matches 1 + sum of Euler’s totient. A geodesic dome has exactly 12 degree-5 vertices at every subdivision (V - E + F = 2) — and the degrees are counted by an existing, independent op (graph_degree_table), because counting them again yourself proves nothing. Phyllotactic spirals look like spirals at any angle, so the neighbour index gaps are counted instead: 7 of 7 peaks are Fibonacci at the golden angle, 2 of 7 for the controls. Moran’s equation solves the dimension before anything is drawn (1.5850 = log3/log2 for Sierpinski) while the existing box-counting op reads 1.6164 off the points — different derivations, different inputs. Non-similarity maps (Barnsley’s fern) are refused. Space-filling curves visit each of 4^n cells once with unit steps, and locality is reported as a table, not a slogan (Hilbert 6.38 at k=32 against sqrt(32) = 5.66; raster 16.07).* |
↑ The measurement ―― 合計は 2·3^depth + 2。種の四つ組だけ 4 方向に反射し、以降は 3 方向 —— ここを 4 のままにすると親を作り直してdepth 3 で 56 個のはずが 88 個になる(一意な円は 56 のままなので、絵は正しく見える)。 (figure labels are in Japanese; the numbers are the same)
↑ フォードの円(分母 12 まで、47 円)。
↑ オイラーの公式 V − E + F = 2 の帰結。
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_theorems_as_pictures/07_parastichy.png)
↑ 黄金角の山は [34, 55, 89, 21, 13, 8, 144] —— すべてフィボナッチ数。
↑ order 5 で 4^5 = 1,024 点をちょうど 1 回ずつ通り、隣り合う点は必ず距離 1。
py -3.11 examples/poc_theorems_as_pictures.py
Source: examples/poc_theorems_as_pictures.py
This run produced 11 figures in total - see them all
Ops used (notes): circle_packing_apollonian · curve_locality · ford_circles · fractal_dimension · geodesic_dome · graph_degree_table · ifs_fractal · ifs_similarity_dimension · neighbour_index_gaps · phyllotaxis_pattern · space_filling_curve
A two-slit fringe and the moire (beat fringe between periodic patterns) of two superposed halftone screens sit in different textbook chapters, but both are the difference of two frequency vectors — one formula, checked here from both ends. Rectangular membrane eigenvalues match pi^2 (m^2/a^2 + n^2/b^2) to 0.00e+00 across the first eight modes of a square, degeneracies included; the widely quoted mnpi is a *product and a different sequence. Nodal line counts are the integers m-1 and n-1, so there is no room for rounding. A circular membrane’s nodal circles sit at the zeros of J0 — the zeros of J’0 are the antinodes, and mistaking them makes a correct op look broken (it did, once). The fringe period lambdaD/d is measured back by a *different op, ratio 0.998-1.003 over four settings, and a screen too small for three fringes is refused rather than quietly returning one. Grating orders round-trip through the existing grating_wavelengths to 550.000000 nm, evanescent orders report propagates=False instead of a fabricated angle, and against the existing fraunhofer_pattern a 50 % duty grating drops its even orders (the Fourier coefficients of a square wave vanish at even harmonics) with a 3rd/1st intensity ratio of sinc(m/2)^2 = 1/9 (measured 0.1140). The moire period is predicted in closed form before drawing and confirmed by FFT (0.948-0.986) — and the search band matters: without an upper cut the peak is the screen’s own 16.7 px, which reads as “the prediction is nowhere near”. Halftoning discards tone but preserves local mean density (0.0517 mean absolute error through a four-period window); engraving ink coverage matches the closed form w/d to 0.0000 across four tones; hatch direction comes from the existing structure_tensor_orientation and lands within 0.3 degrees of a grating whose angle is known by construction; and Lloyd’s energy, measured by the existing stipple_energy, decreases monotonically with the cell mean the exact L2 optimum (zero counterexamples over an exhaustive sweep of ten cells).
↑ One Beat — A Two-Slit Fringe and a Print Moire (beat fringe between periodic patterns) Are the Same Mathematics ―― A two-slit fringe and the moire (beat fringe between periodic patterns) of two superposed halftone screens sit in different textbook chapters, but both are the difference of two frequency vectors — one formula, checked here from both ends. Rectangular membrane eigenvalues match pi^2 (m^2/a^2 + n^2/b^2) to 0.00e+00 across the first eight modes of a square, degeneracies included; the widely quoted mnpi is a product and a different sequence. Nodal line counts are the integers m-1 and n-1, so there is no room for rounding. A circular membrane’s nodal circles sit at the zeros of J0 — the zeros of J’0 are the antinodes, and mistaking them makes a correct op look broken (it did, once). The fringe period lambdaD/d is measured back by a *different op, ratio 0.998-1.003 over four settings, and a screen too small for three fringes is refused rather than quietly returning one. Grating orders round-trip through the existing grating_wavelengths to 550.000000 nm, evanescent orders report propagates=False instead of a fabricated angle, and against the existing fraunhofer_pattern a 50 % duty grating drops its even orders (the Fourier coefficients of a square wave vanish at even harmonics) with a 3rd/1st intensity ratio of sinc(m/2)^2 = 1/9 (measured 0.1140). The moire period is predicted in closed form before drawing and confirmed by FFT (0.948-0.986) — and the search band matters: without an upper cut the peak is the screen’s own 16.7 px, which reads as “the prediction is nowhere near”. Halftoning discards tone but preserves local mean density (0.0517 mean absolute error through a four-period window); engraving ink coverage matches the closed form w/d to 0.0000 across four tones; hatch direction comes from the existing structure_tensor_orientation and lands within 0.3 degrees of a grating whose angle is known by construction; and Lloyd’s energy, measured by the existing stipple_energy, decreases monotonically with the cell mean the exact L2 optimum (zero counterexamples over an exhaustive sweep of ten cells).*
Terms: Moiré — A long-period fringe produced at the difference spatial frequency when two patterns with slightly different pitch or orientation are superposed.
↑ The measurement ―― (m, n) モードの節線は縦 m−1 本・横 n−1 本。円膜の節円は J₀ の零点 / k にある —— J′₀ の零点は腹の位置であって節ではない(取り違えると op が誤っているように見える)。 (figure labels are in Japanese; the numbers are the same)
↑ 縞間隔は λD/d。
↑ ★デューティ 50 % の矩形格子は偶数次が消える(矩形波のフーリエ係数が偶数調波で 0)。
↑ 平均絶対差 0.0517。
↑ Lloyd 反復でセルが等エネルギーに近づく。
py -3.11 examples/poc_beats_fringes_and_screens.py
Source: examples/poc_beats_fringes_and_screens.py
This run produced 13 figures in total - see them all
Ops used (notes): engrave_lines · fraunhofer_pattern · grating_wavelengths · halftone_moire_period · halftone_screen · hatch_field · mosaic_tiles_render · mosaic_tiles_sites · stipple_energy · wave_fringe_period · wave_grating_orders · wave_membrane_mode · wave_mode_frequencies · wave_nodal_lines · wave_two_slit
| A Lorenz plot looks like the same butterfly whether the integrator is 1st or 4th order, and a surface looks smooth whether or not its mean curvature vanishes — so for this family the scoring comes entirely from outside the picture. Against the exact solution expm(At)x0, RK4’s error falls by 16.0 / 16.0 / 16.0 per halving while the Euler control falls by 2.08 / 2.04 / 2.02; at the same dt = 0.01 that is 2.02e-02 against 3.33e-10. The sum of the Lyapunov exponents equals the trace identity -(sigma+1+beta) = -13.666667 to 2.57e-06, independently of the tangent-flow/QR procedure that produced them (lambda1 = 0.9142 against the published 0.906; the conservative control sums to 4.87e-15). The logistic period-doubling points are the exact 3 and 1+sqrt(6), measured at 2.999401 and 3.449260, with delta = 4.7485 against 4.6692 — and the residual is attributed rather than excused: convergence at a bifurcation is algebraic, so the gate is not “the error is small” but “lengthening the burn-in shrinks it 10.9x”. The correlation dimension gives 1.0061 for a circle and 0.6408 for a Cantor set but 1.8789 for a filled square (true value 2); the bias was traced, not guessed — 400 to 3,000 points does not move it, and the cause is the radius window (the default 1st-25th percentile of pair distances runs into the boundary, where the correlation sum saturates), with narrowing moving the estimate from 1.887 to 2.050. The field’s divergence equals tr(A) and its vorticity A10 - A01 to better than 1e-6, as measured by the existing PIV ops, which know nothing about this family; the Lorenz xy slice gives -sigma-1 = -11.000000. A tube swept along a circle is a torus whose analytic volume 2pi^2Rr^2 and area 4pi^2Rr the existing mesh_volume and mesh_area confirm to 0.997 and 0.999, and a curve containing a straight run keeps its radius at 0.150000000000 (parallel-transport frame). The minimal surfaces have | H | medians of 0.00002-0.00014 against controls of 1.00004 (unit sphere) and 0.50000 (unit cylinder) — evidence the gate is not a pass-through. Catenoid and helicoid agree in Gaussian curvature because they are isometric, but that is only a necessary condition. The gyroid’s volume fraction is 0.499928 / 0.499981 / 0.499991 by the body-centred symmetry, while its nodal-approximation residual is reported rather than hidden ( | H | over the principal-curvature scale = 0.1250). |
| ↑ What a Picture Cannot Check — Scoring Dynamical Systems and Minimal Surfaces by Identity and Definition ―― A Lorenz plot looks like the same butterfly whether the integrator is 1st or 4th order, and a surface looks smooth whether or not its mean curvature vanishes — so for this family the scoring comes entirely from outside the picture. Against the exact solution expm(At)x0, RK4’s error falls by 16.0 / 16.0 / 16.0 per halving while the Euler control falls by 2.08 / 2.04 / 2.02; at the same dt = 0.01 that is 2.02e-02 against 3.33e-10. The *sum of the Lyapunov exponents equals the trace identity -(sigma+1+beta) = -13.666667 to 2.57e-06, independently of the tangent-flow/QR procedure that produced them (lambda1 = 0.9142 against the published 0.906; the conservative control sums to 4.87e-15). The logistic period-doubling points are the exact 3 and 1+sqrt(6), measured at 2.999401 and 3.449260, with delta = 4.7485 against 4.6692 — and the residual is attributed rather than excused: convergence at a bifurcation is algebraic, so the gate is not “the error is small” but “lengthening the burn-in shrinks it 10.9x”. The correlation dimension gives 1.0061 for a circle and 0.6408 for a Cantor set but 1.8789 for a filled square (true value 2); the bias was traced, not guessed — 400 to 3,000 points does not move it, and the cause is the radius window (the default 1st-25th percentile of pair distances runs into the boundary, where the correlation sum saturates), with narrowing moving the estimate from 1.887 to 2.050. The field’s divergence equals tr(A) and its vorticity A10 - A01 to better than 1e-6, as measured by the existing PIV ops, which know nothing about this family; the Lorenz xy slice gives -sigma-1 = -11.000000. A tube swept along a circle is a torus whose analytic volume 2pi^2Rr^2 and area 4pi^2Rr the existing mesh_volume and mesh_area confirm to 0.997 and 0.999, and a curve containing a straight run keeps its radius at 0.150000000000 (parallel-transport frame). The minimal surfaces have | H | medians of 0.00002-0.00014 against controls of 1.00004 (unit sphere) and 0.50000 (unit cylinder) — evidence the gate is not a pass-through. Catenoid and helicoid agree in Gaussian curvature because they are isometric, but that is only a necessary condition. The gyroid’s volume fraction is 0.499928 / 0.499981 / 0.499991 by the body-centred symmetry, while its nodal-approximation residual is reported rather than hidden ( | H | over the principal-curvature scale = 0.1250).* |
↑ The measurement ―― 厳密解は expm(At)x₀ なので、誤差は積分器の次数をそのまま出す。★どちらの軌道も絵にすると同じ円に見える —— 絵では次数は確かめられない。 (figure labels are in Japanese; the numbers are the same)
↑ Σλ = -13.66666、閉形式 −(σ+1+β) = -13.66667(差 2.6e-06)。
↑ 円 1・平面 2・カントール log2/log3 = 0.6309。
↑ 同じ op で両方を測っている。
↑ 「これは極小曲面だ」という主張を、作り方を知らない op が採点する。
py -3.11 examples/poc_what_a_picture_cannot_check.py
Source: examples/poc_what_a_picture_cannot_check.py
This run produced 11 figures in total - see them all
Ops used (notes): curve3d_tube_mesh · dynsys_bifurcation_map · dynsys_correlation_dimension · dynsys_lyapunov_spectrum · dynsys_poincare_section · gyroid_isosurface · gyroid_solid_mask · mean_curvature · mesh_area · mesh_volume · minimal_surface · minimal_surface_bend · ode_flow_states · ode_vector_field_grid · piv_divergence · piv_flow_magnitude · piv_vorticity · principal_curvatures · vertex_curvature
What a picture cannot confirm, a closed form can. The material here is a family of generators that never finish on their own - rule 90, Langton’s ant, the Apollonian gasket, the chaos game, Barnsley’s fern, a flow field, reaction-diffusion and plasma - and for all of them the picture is useless as evidence, since a wrong one merely looks more interesting. So each is scored by an identity that holds independently of the drawing. Row n of rule 90 is exactly the parity of the binomial coefficients, with a residual of zero, computed through Lucas’s theorem because accumulating the coefficients themselves silently overflows a 64-bit integer. Langton’s ant, once on its highway, repeats the same diagonal displacement every 104 steps and blackens exactly twelve cells net per period - both exact integers. An early claim of fifty-two cells per period was wrong and is recorded as such: the measured rate did not match, and re-measuring gave twelve, because the ant paints and unpaints within each period. Four mutually tangent circles satisfy the Descartes circle theorem to 1.19e-16, and a separate check confirms that every generated circle actually touches its three parents to 9.20e-07 - picking the wrong branch of the square root produces a chain of circles escaping the outer disc while still looking convincingly fractal, which happened. The flow field is built as the curl of a potential, so its divergence vanishes identically; reaction-diffusion with neither feed nor kill conserves total mass; the diamond-square construction never overwrites its four corners. A seamless loop is not made by returning to the first frame at the end: every time-dependent quantity is written as a function of a phase, so the final frame is the same expression as the first and the seam never exists. One measurement bias is included: the two shafts of the Müller-Lyer figure are exactly equal, yet a naive length estimate returns 223 and 225 pixels - and even with no arrowheads it returns 223 against a true 220, because the anti-aliased skirt is counted as ink. A prediction that the bias would grow with arrowhead length was wrong: it reaches two pixels at twenty and does not move up to eighty, so what creates it is whether a skirt lands on the end, not how long the head is. The illusion drawings were removed in this revision: their checks placed a constant and read the same constant back, with no inference in between, and the pictures themselves were straight lines and rectangles that demonstrate nothing about the library. No new operator was added.
↑ Scoring Endlessly Generated Pictures with Identities, Not with the Eye ―― What a picture cannot confirm, a closed form can. The material here is a family of generators that never finish on their own - rule 90, Langton’s ant, the Apollonian gasket, the chaos game, Barnsley’s fern, a flow field, reaction-diffusion and plasma - and for all of them the picture is useless as evidence, since a wrong one merely looks more interesting. So each is scored by an identity that holds independently of the drawing. Row n of rule 90 is exactly the parity of the binomial coefficients, with a residual of zero, computed through Lucas’s theorem because accumulating the coefficients themselves silently overflows a 64-bit integer. Langton’s ant, once on its highway, repeats the same diagonal displacement every 104 steps and blackens exactly twelve cells net per period - both exact integers. An early claim of fifty-two cells per period was wrong and is recorded as such: the measured rate did not match, and re-measuring gave twelve, because the ant paints and unpaints within each period. Four mutually tangent circles satisfy the Descartes circle theorem to 1.19e-16, and a separate check confirms that every generated circle actually touches its three parents to 9.20e-07 - picking the wrong branch of the square root produces a chain of circles escaping the outer disc while still looking convincingly fractal, which happened. The flow field is built as the curl of a potential, so its divergence vanishes identically; reaction-diffusion with neither feed nor kill conserves total mass; the diamond-square construction never overwrites its four corners. A seamless loop is not made by returning to the first frame at the end: every time-dependent quantity is written as a function of a phase, so the final frame is the same expression as the first and the seam never exists. One measurement bias is included: the two shafts of the Müller-Lyer figure are exactly equal, yet a naive length estimate returns 223 and 225 pixels - and even with no arrowheads it returns 223 against a true 220, because the anti-aliased skirt is counted as ink. A prediction that the bias would grow with arrowhead length was wrong: it reaches two pixels at twenty and does not move up to eighty, so what creates it is whether a skirt lands on the end, not how long the head is. The illusion drawings were removed in this revision: their checks placed a constant and read the same constant back, with no inference in between, and the pictures themselves were straight lines and rectangles that demonstrate nothing about the library. No new operator was added.
↑ The measurement ―― ミュラー・リヤー。2 本の軸は厳密に等長(矢羽根を外すと画素単位で同一)。ところが矢羽根を付けた図で「行の黒い区間」を素朴に測ると、真値 220 に対し 223 画素と 225 画素になります。★これは知覚の話ではなく測る側の偏りで、矢羽根の裾が軸の端に乗るのが原因です。次の図が、その偏りの量と向きです。 (figure labels are in Japanese; the numbers are the same)
↑ 真値はどの点でも 220 画素で等長。
↑ ラングトンの蟻を 21000 歩。
↑ バーンズリーのシダ。
↑ ダイヤモンド–スクエア法(1980 年代の「プラズマ」)。
↑ The animation ―― 継ぎ目の無い循環動画。最後のコマから最初のコマへ戻るところに、切れ目がありません。作り方が要点で、「最後に頭へ戻す」のではなく、時間依存の量をすべてθ の関数にして θ を 0→2π 回すと、t=T は t=0 と同じ式になります —— 継ぎ目は最初から存在しません。
py -3.11 examples/poc_illusions_and_perpetual_drawing.py
Source: examples/poc_illusions_and_perpetual_drawing.py
This run produced 16 figures in total - see them all
Ops used (notes): illusion_muller_lyer · perpetual_apollonian · perpetual_chaos_game · perpetual_elementary_ca · perpetual_flow_field · perpetual_identities · perpetual_ifs_attractor · perpetual_loop · perpetual_loop_seam · perpetual_plasma · perpetual_reaction_diffusion · perpetual_render · perpetual_state · perpetual_step · perpetual_ten_print · perpetual_truchet
An optical illusion is a picture whose answer is already known, so it can be used to grade the measuring instrument rather than the thing measured. Fullseye keeps the generators that ship ground truth and the HALCON-style sub-pixel calipers in the same box, so the whole audit closes in one run - and no new operator was added for it. On the cafe-wall figure a line metrology object recovers a mortar slope of exactly 0.0 degrees with a fit residual of exactly 0.0, where the eye insists the rows converge. But one probe was not enough: tested only at shift 0.25, the instrument looked immovable; across the full 5x4 grid of shift and search half-width it tilts by 0.1429 degrees whenever the half-width reaches the 8 px mortar band, and only at shifts 0.125 and 0.375 - it grabs the far boundary. The failure does not show in the answer, which still reads as almost zero, but in the residual, which moves from 0.00 to 1.70; angle-zero and rms-zero agree across all twenty combinations, which is why the gate belongs on the residual. On Mueller-Lyer the caliper also fails, for a different reason than the eye: the arrowheads put extra edges on the measurement line (two become four), so the instrument pairs an arrowhead stroke rather than the shaft. Declaring the expected width to fuzzy pairing turns that into a visible refusal - a score of 0.9988 without arrowheads against 0.0225 with them - rather than a quiet wrong number. Even the bare shaft measures 223.777 px against a truth of 220, but sweeping the length from 140 to 300 px leaves the offset constant at +3.777 px to within 0.0000 px: proportional error would mean a wrong scale, a constant one means the edge was never defined, since the distance between outer edges exceeds the length of the segment by the stroke width. Ebbinghaus is the clean case - the circle caliper returns two radii differing by 2.5e-12 px, with the systematic sub-pixel offset identical on both so it cancels in the difference. Zollner returns a maximum slope of 4.3e-04 degrees across all seven lines; what actually varies is the amount of evidence, since the alternating hatch direction leaves 13 or 19 of 61 sample points accepted. Poggendorff is where the instrument loses: the two collinear segments come back 0.450 degrees apart, because the edge polarity alternates in runs - a thin line has two edges, and its position is undefined until you say which one, or the middle. Pairing both edges and taking the centre brings that to 0.115 degrees and the residual from 2.07 to 0.10/0.39, but not to zero, and this PoC does not claim it was fixed.
↑ Auditing the Instrument with Illusions - Where a Caliper Fails, and Why ―― An optical illusion is a picture whose answer is already known, so it can be used to grade the measuring instrument rather than the thing measured. Fullseye keeps the generators that ship ground truth and the HALCON-style sub-pixel calipers in the same box, so the whole audit closes in one run - and no new operator was added for it. On the cafe-wall figure a line metrology object recovers a mortar slope of exactly 0.0 degrees with a fit residual of exactly 0.0, where the eye insists the rows converge. But one probe was not enough: tested only at shift 0.25, the instrument looked immovable; across the full 5x4 grid of shift and search half-width it tilts by 0.1429 degrees whenever the half-width reaches the 8 px mortar band, and only at shifts 0.125 and 0.375 - it grabs the far boundary. The failure does not show in the answer, which still reads as almost zero, but in the residual, which moves from 0.00 to 1.70; angle-zero and rms-zero agree across all twenty combinations, which is why the gate belongs on the residual. On Mueller-Lyer the caliper also fails, for a different reason than the eye: the arrowheads put extra edges on the measurement line (two become four), so the instrument pairs an arrowhead stroke rather than the shaft. Declaring the expected width to fuzzy pairing turns that into a visible refusal - a score of 0.9988 without arrowheads against 0.0225 with them - rather than a quiet wrong number. Even the bare shaft measures 223.777 px against a truth of 220, but sweeping the length from 140 to 300 px leaves the offset constant at +3.777 px to within 0.0000 px: proportional error would mean a wrong scale, a constant one means the edge was never defined, since the distance between outer edges exceeds the length of the segment by the stroke width. Ebbinghaus is the clean case - the circle caliper returns two radii differing by 2.5e-12 px, with the systematic sub-pixel offset identical on both so it cancels in the difference. Zollner returns a maximum slope of 4.3e-04 degrees across all seven lines; what actually varies is the amount of evidence, since the alternating hatch direction leaves 13 or 19 of 61 sample points accepted. Poggendorff is where the instrument loses: the two collinear segments come back 0.450 degrees apart, because the edge polarity alternates in runs - a thin line has two edges, and its position is undefined until you say which one, or the middle. Pairing both edges and taking the centre brings that to 0.115 degrees and the residual from 2.07 to 0.10/0.39, but not to zero, and this PoC does not claim it was fixed.
↑ The measurement ―― measure_length は「参照線の法線方向に何 px 探すか」。3 px から 16 px まで振っても角度は 0.0 のままで、採用された点も 4 通りとも 41 点(半幅 3 px, 半幅 6 px, 半幅 10 px, 半幅 16 px)。目地は一様なグレーなので、探索を広げても拾える点は増えません。★これは何も起きなかった図です —- はじめこの図説に「半幅を変えると点の数が変わる」と書きましたが、生成された本文を読み返すとデータと違っていました。証拠の数が実際に動く例は第 5 章のツェルナー(61 点中 13 と 19)のほうです。 (figure labels are in Japanese; the numbers are the same)
↑ ずらし量 5 通り × 探索半幅 4 通りの 20 通り全部を測ったもの。
↑ 青い横罫が測定線(gen_measure_rectangle2)、橙の縦罫が measure_pos が見つけたサブピクセルエッジです。
↑ エビングハウス錯視に円の計測オブジェクト(add_metrology_object_circle_measure)を当てたもの。
↑ 7 本の長い線について、測った傾き(真値 0)と採用されたエッジ点の数を重ねたもの。
py -3.11 examples/poc_calipers_under_illusion.py
Source: examples/poc_calipers_under_illusion.py
This run produced 16 figures in total - see them all
Ops used (notes): add_metrology_object_circle_measure · add_metrology_object_line_measure · apply_metrology_model · create_metrology_model · edge_points · fuzzy_measure_pairing · gen_measure_rectangle2 · illusion_cafe_wall · illusion_ebbinghaus · illusion_ground_truth · illusion_muller_lyer · illusion_poggendorff · illusion_zollner · measure_pairs · measure_pos
Work on point clouds and meshes differs from 2-D work in one decisive way: a pose-alignment step comes before the measurement. That step rotates the shape into whatever orientation makes the discrepancy smallest, so the larger the defect, the more of it the alignment absorbs, leaving a small residual and a part that looks in tolerance. The exhibits in this room try to count what the alignment ate.
Every ground truth here is written as a formula. Solids are built from analytic surfaces combined with boolean operations, chosen so that volume, surface area, wall thickness and curvature are known in closed form. Deformations are known fields (a local dent, a warp, a constant wear along the surface normal), poses are known rotations and translations, and the scan is a uniform sample of the surface with a known density, noise level and occlusion pattern. That is what makes it possible to hold the alignment error and the shape error apart.
The pitfalls specific to 3-D also get counted separately here. A nearest-neighbour distance is biased upward whenever there is noise, because it only ever counts one side. Normal signs are decided by whatever helper computed them. Changing the point density changes the scale of the distance itself. A symmetric shape has no unique pose. Each of these disappears the moment the numbers are folded into one.
Battery cells with the same exterior can hide different causes of swelling or voiding. Known defects were put into stacked electrodes and imaged by CT; only 29.4 % of electrode swelling reached the outside. Cells with matching can heights separate internally, so outside dimensions cannot identify the cause.
↑ Battery cell degradation by CT — the swelling shows outside, the cause stays inside ―― A prismatic lithium-ion cell — stacked electrodes inside an aluminium can — is built with ground truth, degraded by known fields (uniform swelling, local swelling, inter-layer gas voids, electrode misalignment), and then imaged for real: forward projection, beam hardening, photon noise and FBP (filtered back-projection) reconstruction. Only 29.4 % of a 10 % electrode swelling reaches the outside, and a caliper across the centre reads 3.6 times the volume-equivalent mean (+0.235 vs +0.066 mm). Three cells tuned to the same external bulge differ by 0.000 mm in can height yet separate internally (void fraction 0.00 vs 5.20 %, layer flatness 0.0156 vs 0.0784 mm), and the resolution cliff for those internal numbers is set by the 0.120 mm inter-layer gap, not the 0.200 mm electrode — voxel/thickness = 0.30, where the sampling-theorem prediction said 0.80.
Terms: FBP — A tomographic reconstruction method that filters projection data and then back-projects it over all measured angles.
↑ The measurement ―― 真正面(0 度)なら空隙の影までは見える。ただし奥行きに積算されているので厚みも深さも出ない。22 度傾けると層の縞そのものが重なって消える —— 投影では姿勢が結果を決めてしまう。だから断層に落とす。 (figure labels are in Japanese; the numbers are the same)
↑ 端板は周辺で固定なので中央だけが出る。
↑ 一様膨れは平らなまま上がる。
↑ この回の図
↑ 空隙体積は崖の手前から単調に痩せる(部分体積効果)。
py -3.11 examples/poc_battery_ct_degradation.py
Source: examples/poc_battery_ct_degradation.py
This run produced 16 figures in total - see them all
Ops used (notes): beam_hardening_apply · box_sdf · fbp_volume · grid_coords · plane_sdf · projection_angles · radon_volume · render_volume_projection · ring_artifact_apply · sdf_intersect · sdf_subtract · sdf_to_occupancy · sdf_union · vol_bounding_box · vol_edge_probe · vol_fft_lowpass · vol_label · vol_profile_line · vol_region_props · vol_resize · vol_wall_thickness
For distant vehicles, a small image calibration error becomes a physical shift in a bird’s-eye map. Opposite-side range sensors were fused on one grid, tracing the reprojection shift at 22 m. Complementary views create the gain, but growing yaw error makes fusion worse than one sensor.
↑ Fusing two sensors into a bird’s-eye grid — a calibration that passes in pixels turns into metres at range ―― An analytic street scene (a tall lead truck, plus two distant cars each hidden in the other sensor’s shadow) is fused from a left-mirror LiDAR (laser ranging) and a right-mirror depth camera into one bird’s-eye grid, then swept over rotation, translation and timing errors in the extrinsics. Fusion reaches an occupancy IoU of 0.7033 against 0.5417 for the best single sensor — and every bit of that gain comes from complementary visibility, not accuracy. One pixel of reprojection error becomes 0.083 m at 22 m; the cliff is set by the object width, not the 0.2 m cell (IoU drops only 5.8 % when 68 % of the points have already crossed a cell); by 3 deg of yaw the fusion has fallen below the single sensor.
Terms: LiDAR — A sensor that measures range using laser light, commonly by time of flight, to produce a three-dimensional point cloud.; IoU — A 0-to-1 overlap score equal to the area of the intersection of two regions divided by the area of their union.
↑ The measurement ―― 横に 1.80 m 離した 2 センサの「自由と言い切れた領域」。青い帯が LiDAR にしか見えない所、橙の帯がカメラにしか見えない所、灰色は両方。白は真値の障害物。22 m の 2 台は互いの影に 1 台ずつ入っている。 (figure labels are in Japanese; the numbers are the same)
↑ 画像 320 x 240 px、焦点距離 265 px。
↑ 誤差はカメラ側の外部パラメータにだけ入れる。
↑ LiDAR は 22 m の車を 1 セルも返せない(先行車の陰)。
↑ 水平の 2 本は単センサのゼロ点。
py -3.11 examples/poc_bev_sensor_fusion.py
Source: examples/poc_bev_sensor_fusion.py
This run produced 9 figures in total - see them all
Ops used (notes): box_sdf · closing_circle · depth_to_points · esdf · fill_up · fuse · grid_coords · occupancy_grid · project_points · sdf_to_occupancy · sdf_union · voxel_grid_downsample · voxel_iou
Aligning a scan to its design can absorb a defect or move it elsewhere. Known dents and warps were added to a part; even the defect-free control produced 66.5 square mm of false rejected area. Rigid-like deviations shrink, while nearest points jump across edges and create a fictitious dent.
↑ CAD-to-scan deviation inspection — the fit absorbs the defect and invents a dent ―― An analytic machined part carries a known local dent, a bow and a wear band, all applied as exact displacements along the surface normal, and the scan is synthesized with a known pose, noise and one-sided occlusion. After alignment the signed deviation and the out-of-tolerance area show that the local dent is diluted by only 3.7 %, while a 121 um dent appears at the centre of the part where the truth is just 1.2 um (closed form predicts -120 um). What survives depends on how much the defect resembles the six rigid-body degrees of freedom; along the edges the nearest neighbour jumps to the adjacent face, producing 66.5 mm^2 of false out-of-tolerance area even in a defect-free control.
↑ The measurement ―― 真の姿勢を与えた最終行が推定器そのものの床。点-面 ICP との差は姿勢ではなく datum の取り方の差。 (figure labels are in Japanese; the numbers are the same)
↑ 3 枚目が一様に色づくのが第 6 章の主張 —— 位置合わせが反りの平均を吸って、部品全体が下へずれて読める。
↑ 2 次元 Poisson 点過程の最近傍距離の平均 = 0.5/√ρ。
↑ 深さを 30 倍にしても割合は動かないが、広がりを変えると比例して増える。
↑ 上面だけを見ている。
↑ The animation ―― 主図(動画、640 × 360・30 fps・12 秒): 前半は実測点群(だいだい)が CAD の参照点(灰)に重なるまで —— 位置合わせなし → FPFH 粗合わせ → 点-面 ICP の推定姿勢の間を補間して動かす(偏差 RMS 18285.3 → 105.4 → 81.7 µm)。後半は重なった点群を一周し、符号付き偏差(±0.40 mm、だいだい = 足りない / 青 = 余る)で塗る(形が読めるよう陰影を薄く足した)。公差 ±0.10 mm を外れた面積は推定 2300.6 mm²、真値 2514.5 mm²。左手前の 3 本の線は CAD の x・y・z 軸。
py -3.11 examples/poc_cad_scan_deviation.py
Source: examples/poc_cad_scan_deviation.py
This run produced 14 figures in total - see them all
Ops used (notes): box_sdf · chamfer_distance · color_bar · esdf · estimate_normals · estimate_oriented_normals · face_areas · gicp · hausdorff_distance · icp_point2plane · icp_point2point_3d · query_distance · register_fpfh · sphere_sdf · text_box · voxel_grid_downsample
A maize canopy built from analytic leaf surfaces (one-sided area pi/4·L·W, leaf angle and projection coefficient both closed-form) is viewed through an exact nadir z-buffer to measure cover, occlusion and leaf angle. Inverting cover with Beer-Lambert underestimates a true leaf area index of 4.85 by 52.2 % even with the exact extinction coefficient, and stalls just above the ceiling of 2.26 predicted from two-scale clumping (measured 2.70). Occlusion changes nadir cover by not one bit; what breaks the estimate is the folding of projection — an average of 3.49 leaves cover a cell yet are counted once — and quadrupling point density buys only +1.92 in the largest resolvable index.
↑ Crop leaf area from above — folded by projection before it is ever hidden ―― A maize canopy built from analytic leaf surfaces (one-sided area pi/4·L·W, leaf angle and projection coefficient both closed-form) is viewed through an exact nadir z-buffer to measure cover, occlusion and leaf angle. Inverting cover with Beer-Lambert underestimates a true leaf area index of 4.85 by 52.2 % even with the exact extinction coefficient, and stalls just above the ceiling of 2.26 predicted from two-scale clumping (measured 2.70). Occlusion changes nadir cover by not one bit; what breaks the estimate is the folding of projection — an average of 3.49 leaves cover a cell yet are counted once — and quadrupling point density buys only +1.92 in the largest resolvable index.
Terms: Beer–Lambert — The law stating that transmission through a homogeneous absorbing medium decreases exponentially with concentration and optical path length.
↑ The measurement ―― 閉形式 2 pi r h + 4 pi r^2 / pi r^2 h + 4/3 pi r^3 と比べる。2 値化を挟むと面積だけが一方向に膨らむ。 (figure labels are in Japanese; the numbers are the same)
↑ 稈カプセルの符号付き距離場(縦断面、中心が内側 = 負)
↑ 重なりの枚数(遮蔽を無視して全部数えた場合)は真値に乗る。
↑ この回の図
↑ この回の図
py -3.11 examples/poc_crop_phenotyping.py
Source: examples/poc_crop_phenotyping.py
This run produced 17 figures in total - see them all
Ops used (notes): boundary_vertices · capsule_sdf · dem_slope · estimate_normals · face_areas · face_normals · grid_coords · mesh_area · mesh_sample_points · mesh_volume · occupancy_grid · plane_segmentation · render_volume_projection · sdf_to_occupancy
A synthetic die-attach bond line carries five void populations whose volume fraction is held exactly at 3.000 % while only their depth, shape and proximity change, re-imaged as an X-ray CT with a PSF (point spread function), noise and beam-hardening cupping. The naive zero point — threshold, then report void fraction — separates the five conditions by only 0.17 points (2.46 to 2.63 %), yet the interface area shadowed by flat voids is 2.09x that of volume-matched spheres (14.49 vs 6.92 %) and the largest cluster spans 13.1x further when the voids are chained (80.0 vs 6.1 %). The predicted cliff never arrives: at 60 um voxels the void fraction still reads 3.21 % (it only wobbles by 1.36 points with the grid phase), while flatness becomes unmeasurable and the interface-deficit metric drops from 14.16 to 9.78 % — the degradation runs toward ‘pass’, which is the worst possible direction.
↑ Collapsing joint voids into one number — what the number drops is the shape that matters ―― A synthetic die-attach bond line carries five void populations whose volume fraction is held exactly at 3.000 % while only their depth, shape and proximity change, re-imaged as an X-ray CT with a PSF (point spread function), noise and beam-hardening cupping. The naive zero point — threshold, then report void fraction — separates the five conditions by only 0.17 points (2.46 to 2.63 %), yet the interface area shadowed by flat voids is 2.09x that of volume-matched spheres (14.49 vs 6.92 %) and the largest cluster spans 13.1x further when the voids are chained (80.0 vs 6.1 %). The predicted cliff never arrives: at 60 um voxels the void fraction still reads 3.21 % (it only wobbles by 1.36 points with the grid phase), while flatness becomes unmeasurable and the interface-deficit metric drops from 14.16 to 9.78 % — the degradation runs toward ‘pass’, which is the worst possible direction.
Terms: PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.
↑ The measurement ―― 疑似カラーはラベル番号を並べ替えたもの。側面図で界面(上端)に貼りついているのが見える。 (figure labels are in Japanese; the numbers are the same)
↑ ボイド率の列だけを見ると 5 条件は区別できない。
↑ 界面欠損率は 球/中央/散 0.0 % / 球/界面/散 6.9 % / 扁平/界面/散 14.5 % / 球/中央/連 0.0 % / 扁平/界面/連 14.7 % —— 体積率が同じでも 0 から 14.7 % まで動く。
↑ 60 µm では扁平度が測れない(nan なので描けない)。
↑ 塊の数が 24 から落ちた瞬間、最近接間隔は『隣のボイドまで』から『隣の鎖まで』に黙って入れ替わる。
↑ The animation ―― 主図(動画、640 × 360・30 fps・11 秒): 同じボイド率の 2 条件(球・散在・層中央 2.46 % / 扁平・連なり・界面接触 2.63 %)で、xz 断面(橙の枠)を y 方向に掃引しながら 3-D のボイド(2 値化の結果を marching cubes で面に)を回す。色はダイ側界面までの距離 —— 前者は層の中ほど(界面離隔の中央値 60.0 µm)、後者は界面に貼りつく(10.0 µm)。下は同じ断面の観測 CT(上 = ダイ)。合否の 1 個の数字(ボイド率)は 2 つを分けない。z は画面上だけ 2 倍。
py -3.11 examples/poc_ct_void_morphology.py
Source: examples/poc_ct_void_morphology.py
This run produced 13 figures in total - see them all
Ops used (notes): boundary_vertices · box_sdf · color_bar · cylinder_sdf · esdf · face_areas · mesh_volume · morph_dilate3d · plane_sdf · query_distance · sdf_union · sphere_sdf · text_box · vol_boundary_points · vol_gaussian_psf · voxel_to_mips
A synthetic part with known design values (thin walls, a slot, ribs near 45 deg, holes) is voxelised, then wall thickness, support area and tool clearance are measured. Across the 45 deg threshold the support area jumps 185.22 -> 576.10 mm^2 — 3.11x for 0.2 deg — and that step vanishes entirely when the isosurface is taken from a distance field, and loses 12 % under smoothing. Thickness collapses onto a 2-voxel lattice (the inscribed sphere does not help), and clearance errors go both ways: at 0.500 mm voxels the slot appears 2.000 mm wide and admits a tool that does not fit.
↑ Manufacturability from geometry alone — faces that sit on the threshold flip when you smooth them ―― A synthetic part with known design values (thin walls, a slot, ribs near 45 deg, holes) is voxelised, then wall thickness, support area and tool clearance are measured. Across the 45 deg threshold the support area jumps 185.22 -> 576.10 mm^2 — 3.11x for 0.2 deg — and that step vanishes entirely when the isosurface is taken from a distance field, and loses 12 % under smoothing. Thickness collapses onto a 2-voxel lattice (the inscribed sphere does not help), and clearance errors go both ways: at 0.500 mm voxels the slot appears 2.000 mm wide and admits a tool that does not fit.
↑ The measurement ―― 上: 左端の 2 本が薄壁(1.500 mm)とそのあいだのスロット(1.500 mm)、右の三角が補強。下: リブと薄壁の footprint。 (figure labels are in Japanese; the numbers are the same)
↑ 右ほど粗い。
↑ 解析は階段。
↑ 左 = 水平からの傾き(暗い = 0 度 = 最悪、明るい = 90 度 = 垂直)。
↑ 1.600 mm を超えると入らない工具を通し、1.200 mm を切ると入る工具を落とす。
py -3.11 examples/poc_dfm_thickness_overhang.py
Source: examples/poc_dfm_thickness_overhang.py
This run produced 9 figures in total - see them all
Ops used (notes): box_sdf · erfc · esdf · face_areas · face_normals · grid_coords · morph_erode3d · render_shaded · sdf_intersect · sdf_subtract · sdf_to_occupancy · sdf_union · vol_wall_thickness · voxel_to_mesh
A synthetic slope carries an excavation and a deposit of known volume; two epochs of airborne points are differenced both vertically (DoD) and along the local surface normal (M3C2). The expected “cos-θ shrinkage on a slope” never appears — integrating vertical differences over horizontal area cancels the cosine, and the excavated volume stays within -0.011 % from 0 to 40 degrees. What breaks is the registration: when the changed area covers 33 % of the scene, ICP absorbs the change itself and the net volume collapses from a true -30.4 m3 to -3.8 m3, while a no-change control alone already fabricates 83.8 m3 of phantom excavation.
↑ Earthwork on a slope — align first and the scar gets shallower ―― A synthetic slope carries an excavation and a deposit of known volume; two epochs of airborne points are differenced both vertically (DoD) and along the local surface normal (M3C2). The expected “cos-θ shrinkage on a slope” never appears — integrating vertical differences over horizontal area cancels the cosine, and the excavated volume stays within -0.011 % from 0 to 40 degrees. What breaks is the registration: when the changed area covers 33 % of the scene, ICP absorbs the change itself and the net volume collapses from a true -30.4 m3 to -3.8 m3, while a no-change control alone already fabricates 83.8 m3 of phantom excavation.
↑ The measurement ―― 傾斜を 0 から 40 度まで振っても体積の誤差に傾向が無い。cos は積分で約分する。 (figure labels are in Japanese; the numbers are the same)
↑ 真の掘削は 164.2 m3。
↑ 予測式は先に立ててから測った。
↑ 誤差はそのしきい値以上の真値に対する値。
↑ M3C2 の L は法線方向なので cos 25 度 = 0.906 倍だけ浅く出る。
↑ The animation ―― 主図(動画、640 × 360・30 fps・12 秒): 前半は傾斜 25 度の斜面を北の上空から横切り、時期 1 の点群(8 pt/m²、樹冠に当たった点 = 緑)を見せる(動画専用の乱数で作った別の標本)。後半は時期 2 の地形の周りを回り、色を DoD の鉛直差から M3C2 の法線距離へ塗り替える(同じ尺度 ±1.2 m、青 = 下がった)。崩壊中心の深さは DoD 1.212 m / M3C2 1.096 m で比 1.105(sec 25 度 = 1.103)。有意な core の M3C2 土量は掘削 142.3 / 堆積 107.7 m³(真値 164.2 / 133.7)。黒っぽい所は測れなかった core。
py -3.11 examples/poc_lidar_terrain_change.py
Source: examples/poc_lidar_terrain_change.py
This run produced 14 figures in total - see them all
Ops used (notes): chamfer_distance · color_bar · dem_hillshade · dem_slope · estimate_normals · estimate_oriented_normals · fit_plane_3d · icp_point2point_3d · median · ransac_plane · text_box · voxel_grid_downsample
Estimate an animal’s volume from the intersection of multi-view silhouettes (the visual hull) and convert it to weight. A visual hull is always an upper bound, so the question is not whether it is good but that it is always high. The closed form corrected the field rule of thumb: K cameras do not give a K-gon. Under parallel projection each camera contributes two support lines perpendicular to its line of sight, and two opposing cameras contribute the same pair, so the number of distinct tangents is K for even K and 2K for odd K. Three cameras and six cameras are therefore geometrically identical (closed form 1.16772 both times, the measured 0.013 difference being discretisation only), half of an even rig is wasted, and 13 cameras (1.03056) beat 16 (1.04768). Requiring 2 % on weight needs 13 cameras if odd counts are allowed and 24 if not — the naive circular reading of (K/pi)tan(pi/K) says 13, so a team building an even-numbered rig is short by 11. Against the exact ellipse (a/b = 2.76) the measurements sit just above the closed form at every K, confirming it as a lower bound. The centre of this exhibit is separating the error you can buy down from the one you cannot: the phantom between the legs falls from 7.13 % to 1.37 % going from 4 to 48 cameras, while the hollow of the back falls only 3.8 points (56.4 % to 52.6 %) for the same twelvefold increase, because a concavity that never reaches the outline leaves no trace in any silhouette. One pixel of segmentation error is likewise unbuyable, worth +3.21 % per pixel (23.7 kg), matching Steiner’s dV/V = (S/V)delta to a ratio of 1.04 — though the text records that two opposing effects happened to cancel there. The rulers disagree on the winner: volume-derived and allometric weight both prefer 24 cameras with a two-pixel erosion, while the height of the centroid prefers no erosion at all, since the thin legs disappear first. One prediction was wrong: a tape measure wraps the body, so the 3-D convex hull was expected to be the strong method for heart girth, but it measured +55.2 % — the hull of the whole body fills in under the belly and drags the cross-section down to the ground. The convex hull of a section and the section of a convex hull are not the same object. A tooling hole was found and closed on the way: the OpenCV-convention pose helper that space carving requires was unreachable from every public tier, while the name look_at in the public API belonged to the OpenGL-convention version, so grabbing the wrong one returned an empty hull with no exception at all.
↑ Weighing an Animal from Silhouettes — The Error You Can Buy Down, and the One You Cannot ―― Estimate an animal’s volume from the intersection of multi-view silhouettes (the visual hull) and convert it to weight. A visual hull is always an upper bound, so the question is not whether it is good but that it is always high. The closed form corrected the field rule of thumb: K cameras do not give a K-gon. Under parallel projection each camera contributes two support lines perpendicular to its line of sight, and two opposing cameras contribute the same pair, so the number of distinct tangents is K for even K and 2K for odd K. Three cameras and six cameras are therefore geometrically identical (closed form 1.16772 both times, the measured 0.013 difference being discretisation only), half of an even rig is wasted, and 13 cameras (1.03056) beat 16 (1.04768). Requiring 2 % on weight needs 13 cameras if odd counts are allowed and 24 if not — the naive circular reading of (K/pi)tan(pi/K) says 13, so a team building an even-numbered rig is short by 11. Against the exact ellipse (a/b = 2.76) the measurements sit just above the closed form at every K, confirming it as a lower bound. The centre of this exhibit is separating the error you can buy down from the one you cannot: the phantom between the legs falls from 7.13 % to 1.37 % going from 4 to 48 cameras, while the hollow of the back falls only 3.8 points (56.4 % to 52.6 %) for the same twelvefold increase, because a concavity that never reaches the outline leaves no trace in any silhouette. One pixel of segmentation error is likewise unbuyable, worth +3.21 % per pixel (23.7 kg), matching Steiner’s dV/V = (S/V)delta to a ratio of 1.04 — though the text records that two opposing effects happened to cancel there. The rulers disagree on the winner: volume-derived and allometric weight both prefer 24 cameras with a two-pixel erosion, while the height of the centroid prefers no erosion at all, since the thin legs disappear first. One prediction was wrong: a tape measure wraps the body, so the 3-D convex hull was expected to be the strong method for heart girth, but it measured +55.2 % — the hull of the whole body fills in under the belly and drags the cross-section down to the ground. The convex hull of a section and the section of a convex hull are not the same object. A tooling hole was found and closed on the way: the OpenCV-convention pose helper that space carving requires was unreachable from every public tier, while the name look_at in the public API belonged to the OpenGL-convention version, so grabbing the wrong one returned an empty hull with no exception at all.
↑ The measurement ―― 真値 0.7238 m^3 / 738 kg。外接直方体と OBB は上界の中でもいちばん粗い。 (figure labels are in Japanese; the numbers are the same)
↑ 法線は方位 ± 90 度。
↑ くぼみ(輪郭に出ない凹み)は台数に鈍感、脚の間(輪郭に出る凹み)は台数に敏感。
↑ K=12・4.0 mm/px。
↑ 脚の間の空隙はどの 1 枚にも写っている(だから彫れる)。
↑ The animation ―― 動画(270 コマ、560 × 420 px): 牛の周りを 1 周しながら、彫刻に使うカメラの台数を 4 → 48 台(軸周り等間隔、8 m 先)へ増やす。表示は §4 で数えた視体積交差の占有の表面で、真の体に接する面は灰、真に空の所に立つ面(余分)はだいだい。最初の段は真の占有。横腹の帯と脚の間の幽霊(7.13 % → 1.37 %)は台数とともに消えるが、背中のくぼみに被さる蓋は K = 48 でも 52.6 % 埋まったまま(K = 4 で 56.4 %)—— 輪郭に出ない凹みはシルエットに情報が無い。体積は真値の 1.2255 → 1.0339 倍。カメラの仰角は 20 → 46 度へ上げていき、台数の多い段ほど背中の蓋が見える。左下の 3 本は x(体長)・y(体幅)・z(上)、灰の細線は地面の x 軸・y 軸。
py -3.11 examples/poc_livestock_body_volume.py
Source: examples/poc_livestock_body_volume.py
This run produced 14 figures in total - see them all
Ops used (notes): carve · carve_look_at · convex_hull · erosion_circle · mesh_volume · synthesize_silhouette · text_box · vol_rle_bbox · vol_rle_centroid · vol_rle_encode · vol_rle_volume · voxel_to_mesh
A closed triangle mesh is built from the boolean union of a sphere, a torus and a box, then seeded with a known count of each defect class — holes, flipped faces, non-manifold edges, degenerate slivers, duplicated vertices and self-intersection — and tracked with both the topological counts and the volume/area. The Euler characteristic does not move at all for 5 of the 6 classes, and 6 holes plus 6 duplicated faces leave the vertex, edge, face and chi counts identical to the healthy part. Repair does not bring the quantities back: filling a 45-degree cap hole on a sphere changes the surface area by +6.868 % where the closed form predicts -2.145 % (the rim is a staircase, not a circle), and pushing just 6 vertices inward passes all three topological checks while the area is +4.414 % and the volume -0.332 % — a 13-fold disagreement.
↑ Repair the mesh, then measure — the defect count clears, the quantity does not ―― A closed triangle mesh is built from the boolean union of a sphere, a torus and a box, then seeded with a known count of each defect class — holes, flipped faces, non-manifold edges, degenerate slivers, duplicated vertices and self-intersection — and tracked with both the topological counts and the volume/area. The Euler characteristic does not move at all for 5 of the 6 classes, and 6 holes plus 6 duplicated faces leave the vertex, edge, face and chi counts identical to the healthy part. Repair does not bring the quantities back: filling a 45-degree cap hole on a sphere changes the surface area by +6.868 % where the closed form predicts -2.145 % (the rim is a staircase, not a circle), and pushing just 6 vertices inward passes all three topological checks while the area is +4.414 % and the volume -0.332 % — a 13-fold disagreement.
↑ The measurement ―― 健全な部品の χ は 0(種数 1)。χ=2 を合格条件にすると健全品が落ちる。最終行は打ち消し。 (figure labels are in Japanese; the numbers are the same)
↑ ただし順番が両向きに効く —— 溶接前は割れの境界を穴として数え、溶接後は退化三角形を数え損ねる。
↑ 幅ゼロの割れ(重複頂点 18 個)を直した結果。
↑ この回の図
↑ 面積 0 の判定に引っかかるずっと手前で、潰れ面の法線は使いものにならなくなる。
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_mesh_quality_repair/14_decimate_orbit.gif) |
| *↑ The animation ―― 動画(272 コマ、480 × 480 px): 健全な部品(面 11208 枚)の周りを 1.5 周しながら、QEM 簡略化の削減率を 0 → 98 % へ 8 段で上げる。面は平らに塗り、色は面の 3 頂点の平均曲率 | H | (尺度は削減前の 99 パーセンタイル 12.6 /mm で固定)。50 % 削減で体積の誤差は -0.019 % しかないのに曲率の 95 パーセンタイルは 5.89 → 7.72(+31 %)—— 明るい(尖った)面が先に増える。98 % 削減でようやく体積 -4.199 %。左下の 3 本は配列の軸(0 = 貫通穴の軸 = 画面の上、2 = 角柱ボスの側)。* |
py -3.11 examples/poc_mesh_quality_repair.py
Source: examples/poc_mesh_quality_repair.py
This run produced 14 figures in total - see them all
Ops used (notes): decimate_qem · face_normals · fill_holes · inertia_tensor · mesh_area · mesh_edge_lengths · mesh_edge_stats · sdf_subtract · sdf_union · text_box · vertex_curvature · vertex_normals · voxel_to_mesh
Two loads with opposite defects sit on a 1200 x 1000 mm pallet: load A hides a 400 x 400 x 420 mm cavity under a bridging layer plus 20 / 50 / 120 mm slots, while load B is solid but overhangs by 90 mm and tops out 100 mm above the 1800 mm limit. Solving the middle-layer height in closed form (995.0 mm) makes their apparent utilization identical — 62.65 % vs 62.67 %, a 0.02 pt gap — yet A’s 3.11 pt is invisible void and B’s is 2.10 pt overhang plus 0.58 pt over-height, so one load gets restacked and the other gets rejected. Treating the load as one envelope reports 113.90 % (AABB) and 166.44 % (PCA box) for load B, and the IoU between the true solid and the top-down extrusion is 0.9493 / 1.0000 — both read as ‘good agreement’. One knob, the height-map cell size, breaks the answer in two opposite directions: a slot of width w survives only as max(0, 1 - g/w) (a 20 mm slot is gone at g = 40 mm, and at g = w exactly the grid phase decides all-or-nothing — 1 alignment in 5 sees everything), while a load that overhangs by nothing acquires a false overhang of perimeter x g/2 x height that overtakes load B’s genuine 0.0450 m3 from g = 40 mm on.
↑ Pallet load utilization — one number gives voids and overhang the same value ―― Two loads with opposite defects sit on a 1200 x 1000 mm pallet: load A hides a 400 x 400 x 420 mm cavity under a bridging layer plus 20 / 50 / 120 mm slots, while load B is solid but overhangs by 90 mm and tops out 100 mm above the 1800 mm limit. Solving the middle-layer height in closed form (995.0 mm) makes their apparent utilization identical — 62.65 % vs 62.67 %, a 0.02 pt gap — yet A’s 3.11 pt is invisible void and B’s is 2.10 pt overhang plus 0.58 pt over-height, so one load gets restacked and the other gets rejected. Treating the load as one envelope reports 113.90 % (AABB) and 166.44 % (PCA box) for load B, and the IoU between the true solid and the top-down extrusion is 0.9493 / 1.0000 — both read as ‘good agreement’. One knob, the height-map cell size, breaks the answer in two opposite directions: a slot of width w survives only as max(0, 1 - g/w) (a 20 mm slot is gone at g = 40 mm, and at g = w exactly the grid phase decides all-or-nothing — 1 alignment in 5 sees everything), while a load that overhangs by nothing acquires a false overhang of perimeter x g/2 x height that overtakes load B’s genuine 0.0450 m3 from g = 40 mm on.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ この回の図
↑ この回の図
↑ この回の図
py -3.11 examples/poc_pallet_load_utilization.py
Source: examples/poc_pallet_load_utilization.py
This run produced 8 figures in total - see them all
Ops used (notes): aabb · convex_hull · euclidean_cluster · inner_box3 · mesh_volume · voxel_iou
A synthetic pipe carries a pit, a full-circumference thinning band, invert corrosion, a weld bead, ovality and a sagging axis, all at known depths; the in-pipe range sensor is then deliberately run off-axis before the bore is unwrapped. A 4.0 mm axis offset alone flags 44.8 % of a corrosion-free round pipe as wall loss — within 1.84 points of the geometric prediction that an offset e appears as a one-cycle sinusoid of amplitude e — and the fictitious loss volume is 149.9 times the real pit. Removing the one-cycle term removes the fiction, but 79 % of invert corrosion (the commonest defect in sewers) lives in that same harmonic, so its detection falls from 100.0 % to 34.4 %; only constraining the axis the way physics does (a straight line plus a sag) keeps both.
↑ Pipe Wall Loss on the Unwrapped Map — The Axis You Choose Eats the Invert Corrosion ―― A synthetic pipe carries a pit, a full-circumference thinning band, invert corrosion, a weld bead, ovality and a sagging axis, all at known depths; the in-pipe range sensor is then deliberately run off-axis before the bore is unwrapped. A 4.0 mm axis offset alone flags 44.8 % of a corrosion-free round pipe as wall loss — within 1.84 points of the geometric prediction that an offset e appears as a one-cycle sinusoid of amplitude e — and the fictitious loss volume is 149.9 times the real pit. Removing the one-cycle term removes the fiction, but 79 % of invert corrosion (the commonest defect in sewers) lives in that same harmonic, so its detection falls from 100.0 % to 34.4 %; only constraining the axis the way physics does (a straight line plus a sag) keeps both.
↑ The measurement ―― 下の帯の細くなっている所が管底腐食。 (figure labels are in Japanese; the numbers are the same)
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_pipe_wall_loss/03_map_truth.png)
↑ 真の減肉 [mm](縦 = z 0..300 mm、横 = θ 0..360 度。
↑ この回の図
↑ 楕円化は k=2 に立つので分離できる。
↑ この回の図
py -3.11 examples/poc_pipe_wall_loss.py
Source: examples/poc_pipe_wall_loss.py
This run produced 19 figures in total - see them all
Ops used (notes): blob_label · blob_select · cylinder_sdf · esdf · polar_unwrap · ransac_cylinder · render_volume_projection · sdf_subtract · sdf_to_occupancy · spectrum · vol_wall_thickness
Seven synthetic parts are sliced into layers with a known constant per-layer shrinkage strain; warpage is first predicted in closed form from the layer-area history alone, then measured with an incremental layer-birth finite-element solve. A predictor that looks only at the final shape returns exactly zero (2.712e-21), and the history-based closed form lands within 0.4 % on 4 of the 7 shapes — but the moment the area is averaged along the length, where the material sits is lost. Three parts whose layer-area histories match to the last square millimetre bow by 0.4109 / 0.3809 / 0.3271 mm (a 26 % spread), and the force peeling them off the build plate differs by 48x (11.8 vs 573.5 N), flipping the pass/fail verdict.
↑ Warpage lives in the layer history — averaging the area throws the placement away ―― Seven synthetic parts are sliced into layers with a known constant per-layer shrinkage strain; warpage is first predicted in closed form from the layer-area history alone, then measured with an incremental layer-birth finite-element solve. A predictor that looks only at the final shape returns exactly zero (2.712e-21), and the history-based closed form lands within 0.4 % on 4 of the 7 shapes — but the moment the area is averaged along the length, where the material sits is lost. Three parts whose layer-area histories match to the last square millimetre bow by 0.4109 / 0.3809 / 0.3271 mm (a 26 % spread), and the force peeling them off the build plate differs by 48x (11.8 vs 573.5 N), flipping the pass/fail verdict.
↑ The measurement ―― この断面の面積の列だけが、閉形式の入力になる。 (figure labels are in Japanese; the numbers are the same)
↑ この回の図
↑ 断面 2 次モーメントが層数の 3 乗で増えるのに、腕は 1 乗でしか伸びないため。
↑ 首の細さでは崩れない(別図)。
↑ 正 = 接着剤が引っ張られる。
py -3.11 examples/poc_print_warpage_risk.py
Source: examples/poc_print_warpage_risk.py
This run produced 20 figures in total - see them all
Ops used (notes): blob_features · blob_label · box_sdf · grid_coords · render_volume_projection · sdf_to_occupancy · sdf_union · text_box
A ten-capsule articulated body and a moving arm are synthesised so that the true surface-to-surface minimum separation is known in closed form at every instant, and the speed-and-separation-monitoring verdict is then scored against it. Representing the person by one centroid inside a 0.30 m sphere reports the distance +0.166 m too far while a hazard is present and misses 14.3 % of the dangerous frames (28.6 % for a foot-level scanner) — with almost no false alarms, so the failure is strictly one-sided. With a single camera behind the person, 48.6 % of the dangerous frames have the estimate decided by a body part that is not the true nearest one, and 18.1 % are missed; a second camera restores 0 %, but the uncertainty claimed from repeatability, 0.036 m, is only one fifth of the 0.178 m bias that occlusion actually produces.
↑ Human-machine clearance — swap the body for a point, and the hazard vanishes with it ―― A ten-capsule articulated body and a moving arm are synthesised so that the true surface-to-surface minimum separation is known in closed form at every instant, and the speed-and-separation-monitoring verdict is then scored against it. Representing the person by one centroid inside a 0.30 m sphere reports the distance +0.166 m too far while a hazard is present and misses 14.3 % of the dangerous frames (28.6 % for a foot-level scanner) — with almost no false alarms, so the failure is strictly one-sided. With a single camera behind the person, 48.6 % of the dangerous frames have the estimate decided by a body part that is not the true nearest one, and 18.1 % are missed; a second camera restores 0 %, but the uncertainty claimed from repeatability, 0.036 m, is only one fifth of the 0.178 m bias that occlusion actually produces.
↑ The measurement ―― 危険 = 真の距離 < 0.640 m、停止判定 = 推定 < 0.690 m。 (figure labels are in Japanese; the numbers are the same)
↑ 疎にするほど推定は遠くなるので、誤検知が減って見落としが増える。
↑ 横 = x [-0.2, 2.2] m、縦 = y [-1.1, 1.1] m。
↑ この回の図
↑ 人は 10 本のカプセル、機械は 2 本のリンク + 基台、手前は治具台。
py -3.11 examples/poc_safety_clearance.py
Source: examples/poc_safety_clearance.py
This run produced 14 figures in total - see them all
Ops used (notes): annotate3d_label · annotate3d_measure · box_sdf · capsule_sdf · chamfer_distance · distance_line_line · esdf · face_areas · hausdorff_distance · query_distance · render_volume_projection · sdf_to_occupancy · sdf_union
A synthetic room carries known construction errors — leaning walls, a sloping and sagging floor, off-size columns, displaced openings — and is re-measured by a simulated scan from three stations with column shadows, incidence-dependent noise, mixed pixels and registration error. One number for the whole building (mean distance to the design model) moves by only 1.41 mm between a perfect building and a defective one; aligning the cloud in one piece shrinks the wall lean to 69 % of truth and hands 0.89 mrad of tilt to a ceiling that is perfectly level (a closed-form prediction of what the fit absorbs matches the measurement to 0.05 mrad). The cliff is set by the height of the surviving surface rather than by the dropout rate: at the same 90 % loss, dropping points at random costs 0.098 mrad while keeping only the lower band costs 1.234 mrad — 12.6 times worse.
↑ As-built deviation of a room — the compromise pose is handed to the innocent element ―― A synthetic room carries known construction errors — leaning walls, a sloping and sagging floor, off-size columns, displaced openings — and is re-measured by a simulated scan from three stations with column shadows, incidence-dependent noise, mixed pixels and registration error. One number for the whole building (mean distance to the design model) moves by only 1.41 mm between a perfect building and a defective one; aligning the cloud in one piece shrinks the wall lean to 69 % of truth and hands 0.89 mrad of tilt to a ceiling that is perfectly level (a closed-form prediction of what the fit absorbs matches the measurement to 0.05 mrad). The cliff is set by the height of the surviving surface rather than by the dropout rate: at the same 90 % loss, dropping points at random costs 0.098 mrad while keeping only the lower band costs 1.234 mrad — 12.6 times worse.
↑ The measurement ―― いちばん暗い所は 1 か所も見ていない。柱の影はスキャン位置から放射状に伸びる。 (figure labels are in Japanese; the numbers are the same)
↑ (a) と (c) の差は平均で 1.41 mm、Chamfer で 0.50 mm。
↑ 左端の帯が色目盛り(上 +12 mm / 下 -12 mm)。
↑ 「偽の誤差」列は設計どおりに建った建物を同じ手順で測った値。
↑ 点を増やしても系統誤差は薄まらない。
py -3.11 examples/poc_scan_to_bim_asbuilt.py
Source: examples/poc_scan_to_bim_asbuilt.py
This run produced 15 figures in total - see them all
Ops used (notes): box_sdf · chamfer_distance · cylinder_sdf · estimate_normals · euclidean_cluster · grid_coords · plane_sdf · plane_segmentation · sdf_intersect · sdf_subtract · sdf_union · voxel_grid_downsample
A bridge girder (7 planes + 2 cylinders) carries known deflection, section loss, bearing settlement and a crack across three epochs, re-scanned each time from different positions, densities and poses. Re-measuring with zero deterioration already yields a nearest-neighbour “change” of 21.07 mm median and 42.64 mm max, and the false repair volume above a 1 mm threshold (5.098 L) reaches 87 % of the real one (5.882 L). Registering on all points absorbs 0.689 of the mid-span deflection (closed form 2/3) and invents a -1.737 mm uplift at the supports, while the undetermined along-span direction shows up not on the girder planes but only on the bearing cylinder, as a spurious 41.2 mm horizontal shift.
↑ Re-surveying a structure year by year — when the vantage moves, decay appears to advance ―― A bridge girder (7 planes + 2 cylinders) carries known deflection, section loss, bearing settlement and a crack across three epochs, re-scanned each time from different positions, densities and poses. Re-measuring with zero deterioration already yields a nearest-neighbour “change” of 21.07 mm median and 42.64 mm max, and the false repair volume above a 1 mm threshold (5.098 L) reaches 87 % of the real one (5.882 L). Registering on all points absorbs 0.689 of the mid-span deflection (closed form 2/3) and invents a -1.737 mm uplift at the supports, while the undetermined along-span direction shows up not on the girder planes but only on the bearing cylinder, as a spurious 41.2 mm horizontal shift.
↑ The measurement ―― 下フランジの暗い窪みが断面欠損、面全体の淡い変化がたわみ。腹板(法線が水平)にはたわみが出ない ——同じ劣化でも面の向きで見え方が変わる。 (figure labels are in Japanese; the numbers are the same)
↑ 同じ構造物を 3 回測る。
↑ 真のたわみは中央 3.00 mm。
↑ 予測は (ω×(p-c))·n を core 上で積んだだけ。
↑ 押し出し形状の平面は法線に x 成分を持たない。
↑ The animation ―― 動画(640 × 530、15 fps、135 コマ): 橋桁を 2 年で 3 回点検する。左は真の法線方向変化(展開図、真値は 3 時点だけ定義なので点検の間は直線で補間して描いた)、右は法線方向に測った変化で、点検(1 年・2 年)が来たときだけ更新される。下段は代表 3 点の時系列(線 = 真値、点 = 測定)。灰の帯は t2 の差の誤差 RMS の ±2 倍(±3.45 mm)で、代表 3 点のうち欠損の谷(真 -21.4 mm)だけが帯を大きく越える。速度の誤差 RMS は 0.863 mm/年 で、1 mm/年 の進行は 2σ = 1.726 mm/年 の下に沈む。
py -3.11 examples/poc_structure_4d_deterioration.py
Source: examples/poc_structure_4d_deterioration.py
This run produced 12 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · box_sdf · chamfer_distance · cylinder_sdf · data_to_pixel · estimate_normals · euclidean_cluster · fit_circle_3d · fit_plane_3d · grid_coords · hausdorff_distance · plane_sdf · rmse · sdf_intersect · sdf_union · text_box · ticks · voxel_grid_downsample
A bilaterally symmetric mask is synthesised so that both the complete shape and the true mirror plane are known, then one side is broken off with a sphere. With the true plane, symmetric restoration reaches 0.81 mm RMS and beats harmonic hole filling (1.60 mm) — but it loses the moment the plane is off by 1.39 deg (84 arcmin) or 1.41 mm. The error is predicted by the surface-normal component of the mirror displacement (4.6 % relative error; the naive 2d sin a misses by 50.6 %), so the cliff follows from geometry alone. Losing 3.1 % of the points already flips the PCA candidate ranking onto a 90-degree-wrong axis, and on a shape that is not truly symmetric the restoration fabricates 3436 mm3 of ornament or erases 3495 mm3 — even with the exact plane.
↑ Restoring what is missing by symmetry — the plane you assume is the lie you get ―― A bilaterally symmetric mask is synthesised so that both the complete shape and the true mirror plane are known, then one side is broken off with a sphere. With the true plane, symmetric restoration reaches 0.81 mm RMS and beats harmonic hole filling (1.60 mm) — but it loses the moment the plane is off by 1.39 deg (84 arcmin) or 1.41 mm. The error is predicted by the surface-normal component of the mirror displacement (4.6 % relative error; the naive 2d sin a misses by 50.6 %), so the cliff follows from geometry alone. Losing 3.1 % of the points already flips the PCA candidate ranking onto a 90-degree-wrong axis, and on a shape that is not truly symmetric the restoration fabricates 3436 mm3 of ornament or erases 3495 mm3 — even with the exact plane.
↑ The measurement ―― 失われた真値の点から復元点群までの距離(符号なし、6 mm で頭打ち)。対称復元だけが眼窩の形を取り戻す。 (figure labels are in Japanese; the numbers are the same)
↑ 崖は alpha = 1.39 deg(84 分角)。
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_symmetry_restoration/05_offset_cliff.png) |
| *↑ 鏡像点は厳密に 2t 動くが、表面誤差になるのはその法線成分( | n.x | の RMS = 0.50)だけ。* |
↑ 欠損のまま推定した面で復元すると、欠損部の全体が一様にずれる(位置ずれの署名)。
↑ 色は「鏡像 - 真値」。
py -3.11 examples/poc_symmetry_restoration.py
Source: examples/poc_symmetry_restoration.py
This run produced 10 figures in total - see them all
Ops used (notes): detect_reflection_symmetry · fill_holes · fit_plane_3d · icp_point2point_3d · normalize · reflect_points · reflection_symmetry_score
| Motion that never ends still carries equations you can score without watching it end: an infinite zoom returns to the identical picture once you have zoomed by the self-similarity ratio, and a rotating solid returns after 2 pi. Neither loop is made by cutting back to the first frame; both follow from the construction, so the truth is exactly zero difference. The material is Pascal’s triangle mod 2 - the same truth that rule 90 satisfies - and the hierarchy of its gaps has a closed form: with I = floor(u2^D), J = floor(v2^D) and b the position of the highest set bit of I & J, the depth is d = D - b. Halving u shifts I right by one, so b drops by one and d rises by exactly one, which is why zooming never costs resolution: every pixel is decided by an integer bit test rather than by magnifying a raster. Over a loop that zooms eight-fold, frame(T) and frame(0) differ by 0.0e+00. The first core finding is that the measured dimension does not move at all under zoom: the existing fractal_dimension operator returns the same value across six zoom levels with a standard deviation of exactly zero, and where the depth of the approximant matches the pixel grid it equals log2(3) = 1.584962500721 to within 8.9e-16 - an agreement, not an approximation. The second is that the same operator returns 0.0 when handed an approximant finer than the pixels; that zero does not mean there is no structure, it means the structure is finer than the sampling, and indeed the level-9 approximant has zero foreground pixels out of 262,144. The third is that 2 pi does not close in floating point: building the rotation from the angle 2pii/T leaves 1.40e-12 in the normals at i = T because sin(2 pi) is -2.45e-16, while closing the period on the integer index instead gives exactly zero. The fourth is a prediction that was wrong and has been left in: the silhouette area of a cube under orthographic projection is a^2( | cos | + | sin | ), and the measured two per cent residual was read as the bevel that marching cubes puts on the corners - but pushing the camera from a distance of 6 to 96 dropped the residual from 0.128 to 0.004 for the exact cube and the marching-cubes cube alike, so the floor was perspective, not the mesh. The bevel shows up instead in a quantity that distance does not fix: the ratio of maximum to minimum silhouette area converges to sqrt(2) = 1.4142 for the exact cube but stops at 1.3958 for the extracted surface. Separating one residual into two causes came from sweeping the distance. Two smaller findings: the four-fold symmetry of the depth field is exact while the rendered image breaks it by 1.1e-16, which is the order of summation in a 2x2 average rather than any mathematics; and the two labyrinths of the gyroid have a volume ratio of 0.500000000000, which follows from the odd symmetry. No new operator was added. |
| ↑ Endless Zooms and Turning Solids: Making the Return Itself the Ground Truth ―― Motion that never ends still carries equations you can score without watching it end: an infinite zoom returns to the identical picture once you have zoomed by the self-similarity ratio, and a rotating solid returns after 2 pi. Neither loop is made by cutting back to the first frame; both follow from the construction, so the truth is exactly zero difference. The material is Pascal’s triangle mod 2 - the same truth that rule 90 satisfies - and the hierarchy of its gaps has a closed form: with I = floor(u2^D), J = floor(v2^D) and b the position of the highest set bit of I & J, the depth is d = D - b. Halving u shifts I right by one, so b drops by one and d rises by exactly one, which is why zooming never costs resolution: every pixel is decided by an integer bit test rather than by magnifying a raster. Over a loop that zooms eight-fold, frame(T) and frame(0) differ by 0.0e+00. The first core finding is that the measured dimension does not move at all under zoom: the existing fractal_dimension operator returns the same value across six zoom levels with a standard deviation of exactly zero, and where the depth of the approximant matches the pixel grid it equals log2(3) = 1.584962500721 to within 8.9e-16 - an agreement, not an approximation. The second is that the same operator returns 0.0 when handed an approximant finer than the pixels; that zero does not mean there is no structure, it means the structure is finer than the sampling, and indeed the level-9 approximant has zero foreground pixels out of 262,144. The third is that 2 pi does not close in floating point: building the rotation from the angle 2pi*i/T leaves 1.40e-12 in the normals at i = T because sin(2 pi) is -2.45e-16, while closing the period on the integer index instead gives exactly zero. The fourth is a prediction that was wrong and has been left in: the silhouette area of a cube under orthographic projection is a^2( | cos | + | sin | ), and the measured two per cent residual was read as the bevel that marching cubes puts on the corners - but pushing the camera from a distance of 6 to 96 dropped the residual from 0.128 to 0.004 for the exact cube and the marching-cubes cube alike, so the floor was perspective, not the mesh. The bevel shows up instead in a quantity that distance does not fix: the ratio of maximum to minimum silhouette area converges to sqrt(2) = 1.4142 for the exact cube but stops at 1.3958 for the extracted surface. Separating one residual into two causes came from sweeping the distance. Two smaller findings: the four-fold symmetry of the depth field is exact while the rendered image breaks it by 1.1e-16, which is the order of summation in a 2x2 average rather than any mathematics; and the two labyrinths of the gyroid have a volume ratio of 0.500000000000, which follows from the odd symmetry. No new operator was added.* |
↑ The measurement ―― 右は 2 枚の差をそのまま出したもので、全画素が 0(最大差 0.0e+00)。倍率を 8 倍にしたのに同じ絵になるのは、色の巡回(周期 3)が深さの巡回とちょうど噛み合うから。 (figure labels are in Japanese; the numbers are the same)
↑ **空隙の深さ(色 = 深さ、深いほど濃い)。
↑ 左から深さ 5..9。
↑ 正射影ならシルエット面積は a²(|cosθ| + |sinθ|) で、45 度が最大(√2 倍)。
↑ 厳密な立方体は √2 = 1.4142 に収束する(1.4167)のに、marching cubes で取り出した面は 1.3958 で止まる。
↑ The animation ―― 止めどきは呼んだ側が決める。24 コマで 1 周(8 倍)、そこから先は同じ絵が続く。
↑ The animation ―― 1 周 18 コマ。添字の剰余で角度を作っているので、18 コマ目は 0 コマ目と画素単位で同じ。
py -3.11 examples/poc_endless_zoom_and_turning_solids.py
Source: examples/poc_endless_zoom_and_turning_solids.py
This run produced 15 figures in total - see them all
Ops used (notes): fractal_dimension · gyroid_isosurface · mesh_volume · perpetual_loop_seam · phong_shade
The topology and algebra of four dimensions can be scored exactly by ordinary three-dimensional operators already in the box: fitting a circle to a point cloud, the signed volume of a closed mesh, tube meshing and rasterising. The Hopf fibration splits the three-sphere into circles indexed by points of the two-sphere: viewing a point of S^3 as a pair of complex numbers, the fibre over a point of S^2 is the circle q(t) = (cos(theta/2) e^{it}, sin(theta/2) e^{i(t+phi)}), and stereographic projection carries it to an exact circle in space - a Villarceau circle. The first finding is that a four-dimensional circle becomes a two-dimensional point: pushing one fibre through the Hopf map leaves a spread of 8.9e-16 on the sphere, so all 256 sampled points collapse onto one. The second is that the circle-fitting operator recognises it: the projected radius ranges from 0.70 to 7.34 across latitudes, yet the fit residual and the departure from a single plane both stay in the 1e-14 range - a theorem, not an approximation. The third is that two distinct fibres always link exactly once, that the integer emerges from the Gauss integral, and that even the rate of convergence is predictable: doubling the discretisation divides the error by exactly four, measured at 4.01, 4.00, 4.00 and 4.00, confirming the predicted second order. The fourth concerns rotation: in four dimensions a rotation acts in two planes at once, and the motion closes only for rational ratios. A tesseract - 16 vertices, 32 edges, 24 faces, 8 cells, Euler characteristic exactly 0, hypervolume exactly 16 for edge 2 - turned simultaneously in the xy and zw planes returns after sixty steps for ratios 1:2, 2:3 and 3:4 with a difference of 0.0e+00, while the minimum separation along the way stays above 0.23, so it is not the trivial case of nothing having moved. For the golden ratio, refining the step to 400 and running twenty thousand steps still leaves a minimum separation of 0.0257. The period closes because the angle is built from an integer remainder rather than accumulated arithmetic. The fifth finding separates two sources of error in the volume of a tube. Writing two closed forms - one for a circular cross-section on a circular centreline, one for a regular m-gon cross-section on a circular centreline - makes the discrepancy against the second completely independent of m: for a fixed centreline the three cross-sections agree to 7.8e-16. The cross-section’s own contribution then vanishes as 1/m^2 exactly as its closed form says, with ratios 3.99 and 4.00. One prediction was wrong and has been left in: the centreline’s contribution was expected to vanish as 1/n^2, but doubling and quadrupling the sample count divides it by only 2.09 and 4.15, that is as 1/n - and since the perimeter of the inscribed polygon converges as 1/n^2, the residue cannot come from the perimeter but from the thickness at the joints. Consequently neither knob alone suffices: 96 sides alone stalls at -0.001588 and 1600 centreline points alone at -0.003064, while turning both reaches -0.000925. No new operator was added.
↑ Scoring Four-Dimensional Claims with Ordinary Three-Dimensional Operators ―― The topology and algebra of four dimensions can be scored exactly by ordinary three-dimensional operators already in the box: fitting a circle to a point cloud, the signed volume of a closed mesh, tube meshing and rasterising. The Hopf fibration splits the three-sphere into circles indexed by points of the two-sphere: viewing a point of S^3 as a pair of complex numbers, the fibre over a point of S^2 is the circle q(t) = (cos(theta/2) e^{it}, sin(theta/2) e^{i(t+phi)}), and stereographic projection carries it to an exact circle in space - a Villarceau circle. The first finding is that a four-dimensional circle becomes a two-dimensional point: pushing one fibre through the Hopf map leaves a spread of 8.9e-16 on the sphere, so all 256 sampled points collapse onto one. The second is that the circle-fitting operator recognises it: the projected radius ranges from 0.70 to 7.34 across latitudes, yet the fit residual and the departure from a single plane both stay in the 1e-14 range - a theorem, not an approximation. The third is that two distinct fibres always link exactly once, that the integer emerges from the Gauss integral, and that even the rate of convergence is predictable: doubling the discretisation divides the error by exactly four, measured at 4.01, 4.00, 4.00 and 4.00, confirming the predicted second order. The fourth concerns rotation: in four dimensions a rotation acts in two planes at once, and the motion closes only for rational ratios. A tesseract - 16 vertices, 32 edges, 24 faces, 8 cells, Euler characteristic exactly 0, hypervolume exactly 16 for edge 2 - turned simultaneously in the xy and zw planes returns after sixty steps for ratios 1:2, 2:3 and 3:4 with a difference of 0.0e+00, while the minimum separation along the way stays above 0.23, so it is not the trivial case of nothing having moved. For the golden ratio, refining the step to 400 and running twenty thousand steps still leaves a minimum separation of 0.0257. The period closes because the angle is built from an integer remainder rather than accumulated arithmetic. The fifth finding separates two sources of error in the volume of a tube. Writing two closed forms - one for a circular cross-section on a circular centreline, one for a regular m-gon cross-section on a circular centreline - makes the discrepancy against the second completely independent of m: for a fixed centreline the three cross-sections agree to 7.8e-16. The cross-section’s own contribution then vanishes as 1/m^2 exactly as its closed form says, with ratios 3.99 and 4.00. One prediction was wrong and has been left in: the centreline’s contribution was expected to vanish as 1/n^2, but doubling and quadrupling the sample count divides it by only 2.09 and 4.15, that is as 1/n - and since the perimeter of the inscribed polygon converges as 1/n^2, the residue cannot come from the perimeter but from the thickness at the joints. Consequently neither knob alone suffices: 96 sides alone stalls at -0.001588 and 1600 centreline points alone at -0.003064, while turning both reaches -0.000925. No new operator was added.
↑ The measurement ―― ホップ束の繊維 4 本を立体射影して管にしたもの。★4 次元では 4 本とも同じ大きさの円なのに、3 次元へ写すと大きさが変わる —— それでも fit_circle_3d は 4 本すべてを 残差 1.4e-14 で円と認める。どの 2 本も必ず 1 回だけ絡む。 (figure labels are in Japanese; the numbers are the same)
↑ fit_circle_3d に 200 点を食わせた残差。
↑ ガウスの積分で求めた絡み数の、整数 1 からの差。
↑ 真値 B は「正 m 角形断面・円中心線」の体積。
↑ 断面の効果は閉形式どおり 1/m²(比 3.99 / 4.00)で消えるのに、中心線の効果は 1/n(比 2.09 / 4.15)でしか消えない。
↑ The animation ―― 同じ 4 本を視点だけ回して見たもの。★視点の周期は角度でなく整数の剰余で閉じている(24 コマ目が 0 コマ目と同じ式になる)ので、継ぎ目が出ない。絡み方は視点を変えても変わらない —— 絡み数は位相の量だから。
↑ The animation ―― 比 1 : 2(有理)。60 コマでちょうど元に戻る —— 戻ったときの差は 0.0e+00。★角度は整数の剰余で作っているので、有理な比なら継ぎ目が出ない。描いているのは 4 次元の超立方体を2 枚の面で同時に回してから w を落とした影。辺は 32 本とも同じ長さなのに、影では伸び縮みする。
↑ The animation ―― 比 1 : φ(無理、黄金比)。この 60 コマでは戻らない。別に刻みを 400 に細かくして 20,000 歩まで回しても、最小の隔たりは 0.0257 で 0 に落ちない。★角度は整数の剰余で作っているので、有理な比なら継ぎ目が出ない。描いているのは 4 次元の超立方体を2 枚の面で同時に回してから w を落とした影。辺は 32 本とも同じ長さなのに、影では伸び縮みする。
py -3.11 examples/poc_four_dimensions_by_three_d_tools.py
Source: examples/poc_four_dimensions_by_three_d_tools.py
This run produced 10 figures in total - see them all
Ops used (notes): curve3d_tube_mesh · fit_circle_3d · mesh_volume
The author’s idea: put Itokawa (about 535 m) next to the venue buildings and the size becomes tangible. A new op, plateau_lod2_mesh, reads the ViEW2026 venue (Pacifico Yokohama, Minato Mirai) from MLIT’s Project PLATEAU 3D city model (CityGML 2.0, LOD2): the building body and each BuildingPart become one part, and the thematic surfaces (ground, wall, roof) are triangulated (holes bridged to the outer ring and ear-clipped, outward orientation kept). Coordinates go exactly from EPSG:6697 (latitude, longitude, height) through GRS80 Earth-centred Cartesian coordinates into the ENU frame [m] of the tangent plane at the ground centroid. A CRS other than 6697, unknown geometry, a thematic surface not in the table, an unresolved xlink or a height unit other than metres is refused rather than skipped (fail-closed). The seven venue parts (hotel tower with the conference centre = one PLATEAU part, the National Convention Hall, the exhibition hall, the annex hall, the North building and two low-rise annexes) ship as a 112 kB npz; the recipe and its gates stay in the tool. Gates: a synthetic gabled building gives heights of 12 / 4 m, volumes of 2000 / 144 m³ (divergence theorem) and a holed-roof area of 2·20·√41 + 36 − 4 m² in closed form (1e-5), the ellipsoid round trip holds to 1e-6 m, over the seven venue parts the mesh height (highest roof point minus lowest ground point) differs from CityGML measuredHeight by at most 0.045 m (within half the 0.1 m rounding step), the extents measured by a second formula (equirectangular) on coordinates pulled from the raw bytes by regex differ by 0.005 m, and every face points outward. Itokawa’s 535 m length is 2.0 times the exhibition hall’s long side of 263 m and 3.8 hotel-tower heights of 142.1 m. Figures: the venue (left) and Itokawa (right) dropped onto the same 2 m grid seen from above, and a silhouette from the south. Honestly: the bundled 3000-point cloud spans 562 × 298 × 244 m along its principal axes, 1 to 17 % more than the published 535 × 294 × 209 m, a difference in how axes and extents are taken, so it is not a gate. This PLATEAU vintage carries no building names, so the parts were identified by coordinates and checked against public map positions. The geoid height is ignored (6e-6 relative horizontal error). Attribution: processed from the 3D City Model (Project PLATEAU), Yokohama City (FY2020), MLIT Japan (https://www.geospatial.jp/ckan/dataset/plateau-14100-yokohama-city-2020), PDL1.0, compatible with CC BY 4.0. The extraction of seven parts, the coordinate conversion and the triangulation are Fullseye’s own processing, not an MLIT product. The Itokawa points are decimated from the Gaskell shape model (PDS Small Bodies Node).
↑ The Venue Buildings and Asteroid Itokawa on One Ruler — Turning PLATEAU CityGML LOD2 into a Triangle Mesh ―― The author’s idea: put Itokawa (about 535 m) next to the venue buildings and the size becomes tangible. A new op, plateau_lod2_mesh, reads the ViEW2026 venue (Pacifico Yokohama, Minato Mirai) from MLIT’s Project PLATEAU 3D city model (CityGML 2.0, LOD2): the building body and each BuildingPart become one part, and the thematic surfaces (ground, wall, roof) are triangulated (holes bridged to the outer ring and ear-clipped, outward orientation kept). Coordinates go exactly from EPSG:6697 (latitude, longitude, height) through GRS80 Earth-centred Cartesian coordinates into the ENU frame [m] of the tangent plane at the ground centroid. A CRS other than 6697, unknown geometry, a thematic surface not in the table, an unresolved xlink or a height unit other than metres is refused rather than skipped (fail-closed). The seven venue parts (hotel tower with the conference centre = one PLATEAU part, the National Convention Hall, the exhibition hall, the annex hall, the North building and two low-rise annexes) ship as a 112 kB npz; the recipe and its gates stay in the tool. Gates: a synthetic gabled building gives heights of 12 / 4 m, volumes of 2000 / 144 m³ (divergence theorem) and a holed-roof area of 2·20·√41 + 36 − 4 m² in closed form (1e-5), the ellipsoid round trip holds to 1e-6 m, over the seven venue parts the mesh height (highest roof point minus lowest ground point) differs from CityGML measuredHeight by at most 0.045 m (within half the 0.1 m rounding step), the extents measured by a second formula (equirectangular) on coordinates pulled from the raw bytes by regex differ by 0.005 m, and every face points outward. Itokawa’s 535 m length is 2.0 times the exhibition hall’s long side of 263 m and 3.8 hotel-tower heights of 142.1 m. Figures: the venue (left) and Itokawa (right) dropped onto the same 2 m grid seen from above, and a silhouette from the south. Honestly: the bundled 3000-point cloud spans 562 × 298 × 244 m along its principal axes, 1 to 17 % more than the published 535 × 294 × 209 m, a difference in how axes and extents are taken, so it is not a gate. This PLATEAU vintage carries no building names, so the parts were identified by coordinates and checked against public map positions. The geoid height is ignored (6e-6 relative horizontal error). Attribution: processed from the 3D City Model (Project PLATEAU), Yokohama City (FY2020), MLIT Japan (https://www.geospatial.jp/ckan/dataset/plateau-14100-yokohama-city-2020), PDL1.0, compatible with CC BY 4.0. The extraction of seven parts, the coordinate conversion and the triangulation are Fullseye’s own processing, not an MLIT product. The Itokawa points are decimated from the Gaskell shape model (PDS Small Bodies Node).
↑ The measurement ―― 南から見た影絵。ホテル棟 142 m とイトカワの厚み(約 209〜244 m)を同じ縮尺で (figure labels are in Japanese; the numbers are the same)
py -3.11 examples/poc_plateau_venue_lod2.py
Source: examples/poc_plateau_venue_lod2.py
This run produced 2 figures in total - see them all
Ops used (notes): plateau_lod2_mesh
A seeded 196-star synthetic catalogue and a 128 by 128 synthetic image with PSF (point spread function), photon noise and read noise are used; two hot pixels absent from the catalogue are added. Without the true attitude or visible-star rows, triangle angles match all 18 star_detect detections against the shuffled full catalogue, reject the false points, and let kabsch solve the rotation. The result has 16 genuine stars, zero mismatches, 2 of 2 false points rejected and 0.0046-degree attitude error, below the 0.1406-degree angular pixel width. The kabsch and direct-SVD rotations differ by less than 1e-12. Sun or Moon glare, too few stars and trails during fast slew are an explicitly uncomputed ok:false limit. No real catalogue, external image, operator or product name is used.
↑ How One Star Image Reveals Satellite Attitude ―― A seeded 196-star synthetic catalogue and a 128 by 128 synthetic image with PSF (point spread function), photon noise and read noise are used; two hot pixels absent from the catalogue are added. Without the true attitude or visible-star rows, triangle angles match all 18 star_detect detections against the shuffled full catalogue, reject the false points, and let kabsch solve the rotation. The result has 16 genuine stars, zero mismatches, 2 of 2 false points rejected and 0.0046-degree attitude error, below the 0.1406-degree angular pixel width. The kabsch and direct-SVD rotations differ by less than 1e-12. Sun or Moon glare, too few stars and trails during fast slew are an explicitly uncomputed ok:false limit. No real catalogue, external image, operator or product name is used.
Terms: PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.
↑ The measurement ―― 三角形の角距離で全196星表を探索し、ホットピクセル2個を外れ値として棄却した。 (figure labels are in Japanese; the numbers are the same)
↑ 推定値は合成像の検出点から計算し、真値は採点だけに使った。
py -3.11 examples/poc_star_tracker.py
Source: examples/poc_star_tracker.py
This run produced 3 figures in total - see them all
It is easy to borrow an instrument’s name and produce a convenient image. The 32 exhibits in this room instead build raw output from photons, electrons, interference, heat diffusion, scanning and ray tracing, then carry that output through to the quantity normally reported.
The same range fails differently in iToF and dToF through multipath and pile-up; the same particles read at different diameters in SEM and TEM. More TDI stages reveal dark defects while accumulating each synchronisation error across all stages. A model that omits the instrument physics cannot reproduce where the instrument breaks.
Every exhibit is gated by a closed form, public measurement or independent implementation. The aim is not to claim a complete substitute for hardware. It is to state which physics is present and which is still absent, then find when the reported measurement stops being trustworthy before touching the instrument.
Shepp-Logan re-imaged with 180 down to 12 projections, FBP (filtered back-projection) placed beside two nulls: a blank image and unfiltered back-projection. With 12 projections FBP (RMSE 0.2576) is worse than the blank image (0.2420). A check that never looks at the reconstruction — the row sums of the sinogram — caught a -3.34 % mass loss that RMSE could not see; fixing the ramp filter’s DC bin brought it to -0.0099 %.
↑ Where CT Reconstruction Starts to Break as Projections Are Removed ―― Shepp-Logan re-imaged with 180 down to 12 projections, FBP (filtered back-projection) placed beside two nulls: a blank image and unfiltered back-projection. With 12 projections FBP (RMSE 0.2576) is worse than the blank image (0.2420). A check that never looks at the reconstruction — the row sums of the sinogram — caught a -3.34 % mass loss that RMSE could not see; fixing the ramp filter’s DC bin brought it to -0.0099 %.
Terms: FBP — A tomographic reconstruction method that filters projection data and then back-projects it over all measured angles.
↑ The measurement ―― RMSE で見ると 12 本は空白画像 0.2420 より悪い。相関とストリークは別のことを言う。 (figure labels are in Japanese; the numbers are the same)
↑ 零点 A = 空白画像、零点 B = 無フィルタ逆投影(どちらも水平・ほぼ水平)。
py -3.11 examples/poc_ct_fidelity.py
Source: examples/poc_ct_fidelity.py
This run produced 3 figures in total - see them all
Ops used (notes): backproject_sinogram · ellipse_phantom · ellipse_sinogram · filtered_backprojection · projection_angles · radon_transform · rmse · ssim · stat_correlation
Range read from an arrival-time histogram built by bin-integrating a Gaussian at the round-trip time and drawing Poisson samples. With 200 photons the raw peak position errs by 11.02 mm and the gated centroid by 2.29 mm, riding the 31.83 mm/√N bound at 1.00 to 1.07x. With background the plain centroid collapses by two orders (564 mm at SBR 0.031), and the family’s recommended Gaussian fit at 8.82 mm loses to a gated centroid a user can write in six lines.
↑ Ranging by Counting Photons — How Many for How Many Millimetres? ―― The lead figure is a 48-frame, 576 x 432 light-front video. An amber light band from a display window widened to 7 % of the depth span crosses from near to far over a grey return-amplitude image stretched from the 1st to 99.5th percentiles with gamma 0.7. A second figure labels an 8x flying-pixel crop, corner bowing and dark-object noise.
↑ The measurement ―― Flying pixels (8x crop), systematic corner bowing and dark-object range noise shown at a readable scale.
↑ 距離出力|反射振幅(γ=0.5)|ピーク/背景比。
↑ 平面、角、暗い物体。
↑ 背景が無ければ素の重心で足りる。
↑ ゲート重心は CRB に寄り添って落ちる。
py -3.11 examples/poc_dtof_ranging.py
Source: examples/poc_dtof_ranging.py
This run produced 8 figures in total - see them all
Ops used (notes): dtof_cube_depth · dtof_cube_simulate · dtof_depth · gaussian · median · spad_deadtime_apply · tcspc_background_subtract · tcspc_coates_correct · tcspc_simulate
A synthesised coherence-scanning stack with steps of 50 to 500 nm measured back. At 1 % noise the bias stays within 2.4 nm and the standard deviation within 14.1 nm, almost independent of step height. The null (envelope’s maximum sample) errs by exactly half the scan step, and at 0.14 µm, just inside the Nyquist ceiling of 0.15 µm, the noise-free error is already +14.1 nm.
↑ How Accurately a White-Light Interferometer Measures a Nanometre Step ―― A synthesised coherence-scanning stack with steps of 50 to 500 nm measured back. At 1 % noise the bias stays within 2.4 nm and the standard deviation within 14.1 nm, almost independent of step height. The null (envelope’s maximum sample) errs by exactly half the scan step, and at 0.14 µm, just inside the Nyquist ceiling of 0.15 µm, the noise-free error is already +14.1 nm.
↑ The measurement ―― Truth, recovered height, error and low-modulation dropout map; pseudo-colour bars carry units. (figure labels are in Japanese; the numbers are the same)
↑ 包絡線の中心が段差ぶんずれる。
↑ 0.02〜0.12 µm は 0.05 nm 以内で平ら。
↑ centroid は散らばりが gaussian の 1/3〜1/5 なのに総合誤差では上に来る。
↑ 4 種の段差でゲインが揃う = オフセットではなく倍率の誤差。
↑ The animation ―― 48 raw uint8 coherence-scan frames; the localized envelope sweeps over the tilted step.
↑ The animation ―― 動画(148 コマ): 仕込む段差を 0 → 0.90 µm へ連続に増やし、同じ表面を 2 つの方法で測る(雑音なし)。左は低い側・高い側 1 画素ずつのコヒーレンス走査の信号で、縦線は csi_height_map(gaussian)が包絡線から読んだ高さ。右は測った段差 vs 仕込んだ段差。包絡線(だいだい)は全域で対角線に乗り、誤差は最大 8.5e-11 nm —— 包絡線には周期が無いので巻き戻らない。位相シフト法(水色、4 段)は段差 0.153 µm(λ/4 = 0.150 µm の直後)で初めて λ/2 ぶん飛び、以後 λ/2 ごとに鋸の歯になる。
py -3.11 examples/poc_interferometry_step.py
Source: examples/poc_interferometry_step.py
This run produced 8 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · color_bar · csi_contrast_map · csi_design · csi_height_map · csi_stack_simulate · decode_fringe · gaussian · plot_series · synthesize_fringes · text_box · ticks
A 9×9 light field rendered by analytic inverse mapping, with focus-measure, EPI (epipolar-plane image)-slope and two-view block matching scored against the true slope map. The winner beats the constant null by 22x, but beats two-view matching — which uses only 2 of the 81 views — by just 1.6x, and only with cubic interpolation. The default linear interpolation snaps to integer slopes, reading a true 1.30 as 1.4750 (11.9 % in depth).
↑ Depth From a Light Field — A Light Field of Known Depth, Confronted With Its Nulls ―― A 9×9 light field rendered by analytic inverse mapping, with focus-measure, EPI (epipolar-plane image)-slope and two-view block matching scored against the true slope map. The winner beats the constant null by 22x, but beats two-view matching — which uses only 2 of the 81 views — by just 1.6x, and only with cubic interpolation. The default linear interpolation snaps to integer slopes, reading a true 1.30 as 1.4750 (11.9 % in depth).
Terms: EPI — A slice of a light field over one spatial and one angular coordinate, where a 3-D point traces a line whose slope depends on depth.
↑ The measurement ―― 9 by 9 sub-aperture views; disparity is in pixels per adjacent view. (figure labels are in Japanese; the numbers are the same)
↑ Raw micro-image array with an 8-times-area centre inset; display resize uses Lanczos filtering.
↑ Slanted EPI lines encode disparity in px/view; benchmark format is 9 by 9 views.
↑ linear は 1.08/1.15 を 1.0 へ、1.85 を 2.0 側へ引く。
↑ EPI は SNR 20 で既に -35 %。
↑ The animation ―― Twenty-four uint8 refocus frames, 0 to 3 px/view.
py -3.11 examples/poc_lightfield_depth.py
Source: examples/poc_lightfield_depth.py
This run produced 12 figures in total - see them all
Ops used (notes): color_bar · lf_depth_from_focus · lf_disparity_to_depth · lf_epi · lf_epi_slope · lf_refocus · lf_to_mla
Diffuse and specular components synthesised as s/p terms with the degree of polarisation from the Fresnel equations, and the separation scored against them. At 20 degrees of incidence the polarisation method wins by only 1.2x. The diffuse error matches the closed form R_p·E exactly and vanishes at the Brewster angle of 56.31 degrees — the diffuse term of polarization_separate is systematically high by that amount.
↑ Stripping Specular Reflection With Polarisation — Truth From the Fresnel Equations ―― Diffuse and specular components synthesised as s/p terms with the degree of polarisation from the Fresnel equations, and the separation scored against them. At 20 degrees of incidence the polarisation method wins by only 1.2x. The diffuse error matches the closed form R_p·E exactly and vanishes at the Brewster angle of 56.31 degrees — the diffuse term of polarization_separate is systematically high by that amount.
↑ The measurement ―― Half-resolution analyzer samples separated from the mosaic (one quarter of the pixels each). (figure labels are in Japanese; the numbers are the same)
↑ Stokes fields, polarization degree, hue-wheel angle and the minimum-polarized component.
↑ R_p がブリュースター角で 0 を横切る。
↑ 実測と閉形式が重なる。
↑ 残差はローブと同じ形。
py -3.11 examples/poc_polarization_specular.py
Source: examples/poc_polarization_specular.py
This run produced 7 figures in total - see them all
Ops used (notes): fresnel_reflectance · polarization_dolp_map · polarization_render · polarization_separate · polarization_stokes · rmse
A megawatt PV array synthesised from a closed-form steady-state heat balance, seeded with known electrical faults (an in-cell hotspot, a bypassed string) and with real-but-not-electrical temperature differences (shading, soiling). A hotspot dissipating 320 W/m² is 11.20 K before dilution yet reaches the camera as 4.23 K — and the dilution comes not from the lateral conduction we expected (x0.960) but from the camera (x0.511). A control scene containing no electrical fault at all still raises 6 blobs when the whole-image mean is the reference (1 healthy, 5 non-fault). The cliff is set by the minimum-area gate rather than the threshold: the hotspot disappears at 1.0 m/s of wind, matching the area-based prediction of 1.1 m/s while the peak-based prediction of 3.0 m/s is wrong. Row-to-row shading warms the sunlit cells of the same string by +4.68 K, only 0.87 K away from the genuine string fault at +5.55 K.
↑ Drone Thermography of a PV Plant — You Think You Are Measuring Temperature Difference, but You Are Measuring Wind and Viewing Angle ―― A megawatt PV array synthesised from a closed-form steady-state heat balance, seeded with known electrical faults (an in-cell hotspot, a bypassed string) and with real-but-not-electrical temperature differences (shading, soiling). A hotspot dissipating 320 W/m² is 11.20 K before dilution yet reaches the camera as 4.23 K — and the dilution comes not from the lateral conduction we expected (x0.960) but from the camera (x0.511). A control scene containing no electrical fault at all still raises 6 blobs when the whole-image mean is the reference (1 healthy, 5 non-fault). The cliff is set by the minimum-area gate rather than the threshold: the hotspot disappears at 1.0 m/s of wind, matching the area-based prediction of 1.1 m/s while the peak-based prediction of 3.0 m/s is wrong. Row-to-row shading warms the sunlit cells of the same string by +4.68 K, only 0.87 K away from the genuine string fault at +5.55 K.
↑ The measurement ―― 「偽」= 故障でも影でも汚れでもない場所に出た塊。しきい値 3.0 K、風速 1.0 m/s。 (figure labels are in Japanese; the numbers are the same)
↑ U(v) = 1.75·(5.7 + 3.8v + h_rad)。
↑ ε(θ) は Fresnel(等価屈折率 1.8)。
↑ 上段の塊はすべて偽か非故障の温度差。
↑ Module-level correction preserves the detection chapters and their thresholds.
py -3.11 examples/poc_pv_thermal_survey.py
Source: examples/poc_pv_thermal_survey.py
This run produced 10 figures in total - see them all
Ops used (notes): beer_lambert_transmittance · blob_features · blob_label · blob_select · color_bar · fresnel_dielectric · gauss_filter · median · overlay_mask · surface_form_remove · volume_downsample · warp_by_plane · zoom_image_factor
DN noise uses EMVA-1288-style variance and is not double-counted with NETD. Assuming emissivity 0.60 plus or minus 0.05, reflected temperature 300 plus or minus 5 K and unmeasured rho=0.7, correlation-blind RSS gives 4.5086 K while GUM with covariance gives 5.6518 K. Nominal 95% coverage is 88.03% for RSS and 94.44% for correlation-aware Monte Carlo.
↑ A Thermal Image Is Not a Temperature Image — Emissivity, Reflection, and an Uncertainty That Does Not Add ―― DN noise uses EMVA-1288-style variance and is not double-counted with NETD. Assuming emissivity 0.60 plus or minus 0.05, reflected temperature 300 plus or minus 5 K and unmeasured rho=0.7, correlation-blind RSS gives 4.5086 K while GUM with covariance gives 5.6518 K. Nominal 95% coverage is 88.03% for RSS and 94.44% for correlation-aware Monte Carlo.
Terms: EMVA 1288 — A standard measurement procedure and set of quantities for sensitivity, noise, saturation, and non-uniformity of machine-vision image sensors.; GUM — The international guide on how to evaluate and report measurement uncertainty, combining the spread of each input into the uncertainty of the result.
↑ The measurement ―― LWIR・350 K・ε=0.95。校正表と直接積分の相対差は最大 8.9e-16。以下の誤差はすべてこの床の上。 (figure labels are in Japanese; the numbers are the same)
↑ n = d ln L_bb/d ln T。
↑ LWIR・T_obj = 350 K・T_refl = 300 K。
↑ 20000 試行 / 点。
↑ 止まるのは校正表の範囲外に落ちたときだけ。
py -3.11 examples/poc_thermal_radiometry.py
Source: examples/poc_thermal_radiometry.py
This run produced 18 figures in total - see them all
Ops used (notes): beer_lambert_transmittance · interp_linear · mat_eigh · noise_sigma · photon_uncertainty · poly_fit · stat_correlation · stat_covariance · stat_describe · stat_histogram
Surface temperature after a flash, generated from the exact 1-D heat-conduction solution, with delamination depth estimated from the image sequence. Where the diameter is at least four times the depth the estimate lands within a few percent; at 0.5 mm deep and 2 mm across it is off by +627 %. The cause is not lateral diffusion but the fitting window: shortening it from 25 s to 4 s brings that case back to -9 %.
↑ Depth of a Subsurface Defect by Pulsed Thermography ―― Surface temperature after a flash, generated from the exact 1-D heat-conduction solution, with delamination depth estimated from the image sequence. Where the diameter is at least four times the depth the estimate lands within a few percent; at 0.5 mm deep and 2 mm across it is off by +627 %. The cause is not lateral diffusion but the fitting window: shortening it from 25 s to 4 s brings that case back to -9 %.
↑ The measurement ―― 右下三角(直径が深さの 4 倍以上)は数 %。左上は横拡散で壊れる。 (figure labels are in Japanese; the numbers are the same)
↑ Viewer-gamma grayscale; NETD 0.02 K, low-frequency NUC residual 0.08 K RMS, four dead pixels.
↑ Sequential turbo-like depth scale in millimetres; signed error at right.
↑ 早期の勾配は -1/2。
↑ The animation ―― Forty-eight uint8 cooling frames after a non-uniform flash.
py -3.11 examples/poc_thermography_ndt.py
Source: examples/poc_thermography_ndt.py
This run produced 7 figures in total - see them all
Ops used (notes): color_bar
The quantum efficiency η, system gain K, dark noise σ_d, saturation, SNR, dynamic range, DSNU (dark signal non-uniformity) and PRNU (photo-response non-uniformity) on a camera datasheet are numbers produced by the procedure of EMVA 1288 (image-sensor characterization standard) Release 4.0 Linear. This exhibit synthesises a sensor from a physical model (Poisson photons → electrons → dark noise and dark current → gain K → quantisation and saturation, with column, row and pixel DSNU and a PRNU pattern) and recovers the planted values with the standard’s estimators, the new op family sensorchar (10 ops). Synthesis and estimation use different equations. Gates: recovery — from the photon transfer (eq. 50) of mean and temporal variance of image pairs at equal exposure (eqs. 16, 18) K to -0.66 %, from dark images σ_d to +0.13 % (eq. 53), from the response slope η to +0.67 % (eq. 52), dark current to +0.6 %, the column/row/pixel spatial variances (eq. 42) and DSNU +0.6 %, PRNU -0.1 % / identities — SNR(μ_p.min) = 1 (eqs. 26 and 21 are written independently) and the ideal sensor = √μ_p (eq. 23) to machine precision, slope 1 → 1/2 (eq. 22) / validity — below a dark variance of 0.24 DN² σ_d is not estimated (eqs. 53, 54), and for a flat sensor that is where the estimate really breaks / published values — on manufacturers’ published EMVA 1288 data for 38 models the maximum SNR = √μ_e.sat (eq. 55) holds within 0.49 dB for all / linearity — the closed form of the linearity error (eqs. 58–63) matches an independent weighted least-squares fit to 3.9e-13 / defect pixels — the 9 planted ones are counted back. Found on the way: taking σ_d from the photon-transfer intercept is off by +53.5 % (the standard measures it directly from dark images). A DSNU pattern dithers the quantiser, so even at K = 0.10, which the standard calls unmeasurable, σ_d comes out within 0.7 % (flat: 29.8 %) — the standard’s limit is conservative. Ledger entry no. 8 gives a DR of 68.9 dB from its 21.0 ke⁻ saturation and 7 e⁻ dark noise, but lists 74 dB — one of the two columns seems to come from other conditions (not checked; the ledger is left as is and the gate names it). The rest of the standard (added 2026-10-13): on the simulated camera described in section 5 of the standard (640 x 480, two 1.5 DN sinusoids in the DSNU, illumination falling 3 % towards the corners) the PRNU reads 0.831 % without the section 8.1 highpass filter (planted 0.5 %, +66 %) and 0.497 % with it (lower by the filter’s white gain 0.9885); the DSNU drops from 21.25 to 18.83 e- because the 25-pixel sinusoid passes at 38 %, as the transfer function predicts; the section 8.6 spectrogram recovers the sinusoid amplitudes as 1.46 / 1.50 DN with equation 72; the factor 0.92 printed in equation 86 of appendix C.5 disagrees with the measured 0.960 (Parseval, 1 - 1/25). The equation 51 linearity check (mean relative deviation of the local slope < 3 %) needs pixels: a linear 128 x 160 sensor failed it once in six runs, a 512 x 640 one never. Honestly: a synthetic sensor, uniform illumination in sections 1-7, and the published values are rounded integers without K, so the DR check treats the quantisation noise as zero. How in-camera processing moves the numbers is the sequel poc_emva1288_in_camera_processing. 14 gates, about 4 s.
↑ Measuring a Camera Without Buying One — Recovering Quantum Efficiency, Gain and Dark Noise from a Planted Sensor with the EMVA 1288 (image-sensor characterization standard) Procedure ―― The lead figure places 640 x 480 dark, flat and saturated frames at native scale, exposing 1 DN row, 1.5 DN column and 4 DN pixel fixed pattern plus nine planted defective pixels.
Terms: EMVA 1288 — A standard measurement procedure and set of quantities for sensitivity, noise, saturation, and non-uniformity of machine-vision image sensors.; DSNU — Spatial pixel-to-pixel variation of the mean dark signal when an image sensor receives no light.; PRNU — Spatial pixel-to-pixel variation in an image sensor’s response under uniform illumination.
↑ The measurement ―― 平坦画像2枚の差(時間雑音)|平坦平均−暗平均(空間雑音)|6σ欠陥画素図。固定模様を無効化する検査では中央の空間模様が消え、ゲートは赤になる。 (figure labels are in Japanese; the numbers are the same)
↑ 規格の模擬カメラ。
↑ 白い DSNU は平らな床(高さ = 標準偏差 1.5 DN)、正弦は鋭い山(横 0.04・縦 0.2 c/px)。
↑ 線形なセンサを乱数を変えて撮り、式 51(局所の傾きの相対偏差の平均)を出す。
↑ 暗画像の分散が 0.24 DN² より小さいと、規格は σ_d を推定しない(式 53・54)。
↑ The animation ―― 露光を上げながら同じ露光で 2 枚ずつ撮り、平均と時間分散(2 枚の差から、式 18)を 1 点ずつ置く。直線の傾きがシステムゲイン K = 0.2086 DN/e⁻(仕込み 0.21)。飽和の近くでは分散が潰れるので、規格は 0〜70 % の点だけで当てる。
py -3.11 examples/poc_emva1288_sensor.py
Source: examples/poc_emva1288_sensor.py
This run produced 10 figures in total - see them all
Ops used (notes): emva_dark_current · emva_defect_pixels · emva_dynamic_range · emva_exposure_for_snr · emva_highpass · emva_linearity_error · emva_pair_statistics · emva_photon_exposure · emva_photon_transfer · emva_ptc_linearity_check · emva_quantum_efficiency · emva_sensitivity_threshold · emva_snr_curve · emva_spatial_nonuniformity · emva_spectrogram · emva_total_snr · spectrogram · text_box
Miscounting pores in a weld radiograph leads to the wrong weld grade. A fixed cut-off also counts the edge of the weld cap and reports 124 blobs. Estimating the background before detection keeps only real pores, and far fewer images are graded wrongly.
↑ Porosity in Weld Radiographs — Closing With the Share of Images Misgraded by One Class ―― A radiograph drawn in closed form with Beer–Lambert (exponential absorption law): 10 mm plate, arc-shaped reinforcement, spherical pores, plus scatter, unsharpness and film grain. The fixed-threshold baseline counts the weld toe as pores (124 blobs, total area 15.19 mm² against 7.93 true). The detection cliff is predictable from CNR = 16.12·d²: Rose’s CNR = 4 misses (0.50 mm), while the prediction that includes smoothing and the 3 px minimum area gives 0.58 mm against 0.57 mm measured. The 9 px window cap of the background-estimation op (rectangular opening) becomes a cliff: detection drops below 50 % from 2.0 mm and reaches 0 % at 2.5 mm — choosing the op chooses the measuring range. Scatter at SPR = 1 shrinks the volumetric diameter by (1+SPR)^(-1/3): -22.6 % measured against -20.6 % predicted. Images misgraded by one class: 90 % for the baseline, 23 % with the volumetric diameter.
Terms: Beer–Lambert — The law stating that transmission through a homogeneous absorbing medium decreases exponentially with concentration and optical path length.
↑ The measurement (figure labels are in Japanese; the numbers are the same)
↑ 小さい側の崖は CNR で予測どおり。
↑ 面積径は小さい側で縮む(等高線が内側)。
↑ この回の図
↑ 体積径は (1+SPR)^(-1/3) で縮む。
py -3.11 examples/poc_weld_radiograph_porosity.py
Source: examples/poc_weld_radiograph_porosity.py
This run produced 12 figures in total - see them all
Ops used (notes): blob_features · blob_label · blob_overlay · blob_select · estimate_noise · gauss_image · gen_measure_rectangle2 · gray_opening_rect · identity · log_image · measure_pairs · measure_pos · median_rect · sk_rolling_ball · xsitk_grayscale_grindpeak
Shine X-rays on a powder mixture and the detector records the Debye rings (powder-diffraction rings) of every phase on top of each other. The new module pxrd (14 ops), with no learning, calibrates the detector’s centre, distance and tilt on the rings of a Si standard (at a 3° tilt: centre 0.011 px, distance 0.0009 %, tilt 3.004°, direction −40.01°, rms 0.019°), integrates azimuthally into a 2θ profile, takes the crystallite size from the peak widths by Scherrer (354 Å, truth 350 Å), peels the phases off one at a time against a reference dictionary built from CIFs (NNLS forward selection, in the order NaCl > CaF₂ > corundum, rwp 0.546 → 0.179), indexes the 8 peaks left in the residual as cubic F with a = 4.2170 Å (truth 4.2170), and names MgO among four candidates (MgO, NiO, CaO, KCl). The four-phase fractions (35 / 30 / 20 / 15 wt%) come back to within 0.04 wt%. The external reference is the NIST SRM 640g certificate: from a = 0.543 110 9 nm and Cu Kα1, Bragg plus the extinction rules reproduce its 11 tabulated lines to 0.00044° and produce no other line up to 140°. The phases are COD CIFs (CC0, kept outside the repository). Traps: integrating a 5°-tilted detector as if flat turns the rings into ellipses and splits 10 peaks into 23; a sample lattice only 0.4 % larger than the reference collapses the fractions, which return once the lattice is refined. Honestly: synthesis and analysis share one physical model, so 0.04 wt% only shows that the model’s quantities survive geometry, pixels, noise and overlap. The leak was measured by deliberate mismatch: a wrong peak shape costs 0.33 wt%, ions versus neutral atoms 0.06 wt%, a crystallite size off by −29 % 0.97 wt%. Microabsorption, preferred orientation and anomalous dispersion are not in the model, and there is no external reference for intensities (measured relative intensities) yet.
↑ Peeling Phases One at a Time off a 2-D Powder X-ray Diffraction Image — Calibration, Azimuthal Integration, NNLS and the Unknown Phase in the Residual; References from the NIST SRM 640g Certificate and COD CIFs, with the Shared-Model Leak Measured by Deliberate Mismatch ―― Shine X-rays on a powder mixture and the detector records the Debye rings (powder-diffraction rings) of every phase on top of each other. The new module pxrd (14 ops), with no learning, calibrates the detector’s centre, distance and tilt on the rings of a Si standard (at a 3° tilt: centre 0.011 px, distance 0.0009 %, tilt 3.004°, direction −40.01°, rms 0.019°), integrates azimuthally into a 2θ profile, takes the crystallite size from the peak widths by Scherrer (354 Å, truth 350 Å), peels the phases off one at a time against a reference dictionary built from CIFs (NNLS forward selection, in the order NaCl > CaF₂ > corundum, rwp 0.546 → 0.179), indexes the 8 peaks left in the residual as cubic F with a = 4.2170 Å (truth 4.2170), and names MgO among four candidates (MgO, NiO, CaO, KCl). The four-phase fractions (35 / 30 / 20 / 15 wt%) come back to within 0.04 wt%. The external reference is the NIST SRM 640g certificate: from a = 0.543 110 9 nm and Cu Kα1, Bragg plus the extinction rules reproduce its 11 tabulated lines to 0.00044° and produce no other line up to 140°. The phases are COD CIFs (CC0, kept outside the repository). Traps: integrating a 5°-tilted detector as if flat turns the rings into ellipses and splits 10 peaks into 23; a sample lattice only 0.4 % larger than the reference collapses the fractions, which return once the lattice is refined. Honestly: synthesis and analysis share one physical model, so 0.04 wt% only shows that the model’s quantities survive geometry, pixels, noise and overlap. The leak was measured by deliberate mismatch: a wrong peak shape costs 0.33 wt%, ions versus neutral atoms 0.06 wt%, a crystallite size off by −29 % 0.97 wt%. Microabsorption, preferred orientation and anomalous dispersion are not in the model, and there is no external reference for intensities (measured relative intensities) yet.
Terms: Debye rings — Concentric diffraction lines formed on a 2-D detector when randomly oriented crystallites produce a diffraction cone for each lattice spacing.
↑ The measurement ―― the detector image coloured by phase (1:1 pixels, lossless PNG) (figure labels are in Japanese; the numbers are the same)
↑ the observed detector image (background removed, gamma 0.5)
↑ integrated profile, NNLS fit and each phase’s contribution
↑ この回の図
↑ この回の図
py -3.11 examples/poc_pxrd_phase_peel.py
Source: examples/poc_pxrd_phase_peel.py
This run produced 9 figures in total - see them all
Ops used (notes): axes_frame · axes_transform · azimuthal_integrate · cif_read · cubic_index · cubic_prototype · debye_ring_image · detector_calibrate · detector_two_theta · difference · diffraction_peaks · grid_lines · intensity · legend_box · nice_ticks · phase_dictionary · phase_fractions · phase_peel · plot_series · powder_reflections · scherrer_size · text_box · ticks · unexplained_peaks
A black-glossy resin dome (n 1.57) and a glass lens (n 1.52), measured from one raw mosaic of a 4-direction on-chip polariser sensor; the truth is the analytic normals and heights used to render them. Median normal error 0.53 deg on the dome and 1.09 deg on the lens, dome height RMS 0.62 px out of 37.9 px; photometric stereo with four lights sees only the highlights on the same dome and is off by a median 26.4 deg (50x; 2,408 of 7,921 pixels get no normal at all). The ambiguity is kept: before disambiguation every pixel has four normals (two zeniths straddling Brewster’s angle x the pi azimuth), the two zeniths merging on the 57.5 deg Brewster ring; convexity plus the Brewster ridge picks one. A calibration ball gives n = 1.5691 (truth 1.57) by fitting the Fresnel curve, while reading tan(theta) at the DoLP maximum gives 1.715 - a noise spike. Where it breaks: assuming n = 1.50 costs 0.89 deg and 1.40 costs 2.21 deg (exactly 0 at the truth); noise concentrates on the Brewster ring (1.27 deg) and at the apex (4.87 deg) against 0.57 deg on the flanks; 30 % diffuse light mixed in costs 7.4 deg (the two polarisations are 90 deg apart and cancel).
↑ Measuring Black-Gloss and Glass from Polarisation - No Shading, but the Polarisation Remembers the Slope ―― A black-glossy resin dome (n 1.57) and a glass lens (n 1.52), measured from one raw mosaic of a 4-direction on-chip polariser sensor; the truth is the analytic normals and heights used to render them. Median normal error 0.53 deg on the dome and 1.09 deg on the lens, dome height RMS 0.62 px out of 37.9 px; photometric stereo with four lights sees only the highlights on the same dome and is off by a median 26.4 deg (50x; 2,408 of 7,921 pixels get no normal at all). The ambiguity is kept: before disambiguation every pixel has four normals (two zeniths straddling Brewster’s angle x the pi azimuth), the two zeniths merging on the 57.5 deg Brewster ring; convexity plus the Brewster ridge picks one. A calibration ball gives n = 1.5691 (truth 1.57) by fitting the Fresnel curve, while reading tan(theta) at the DoLP maximum gives 1.715 - a noise spike. Where it breaks: assuming n = 1.50 costs 0.89 deg and 1.40 costs 2.21 deg (exactly 0 at the truth); noise concentrates on the Brewster ring (1.27 deg) and at the apex (4.87 deg) against 0.57 deg on the flanks; 30 % diffuse light mixed in costs 7.4 deg (the two polarisations are 90 deg apart and cancel).
↑ The measurement ―― 鏡面の DoLP は Brewster 角でちょうど 1 になり、その両側に同じ値が 2 回出る。拡散は単調だが弱い(最大 (n²−1)/(n²+1))。 (figure labels are in Japanese; the numbers are the same)
↑ 左 = 黒光沢樹脂のドーム(n = 1.57)、右 = ガラスのレンズ(n = 1.52)。
↑ 鏡面の DoLP は Brewster を挟んで 2 つの天頂角を持ち、方位は偏光子にとって π が同じ。
↑ 屈折率の誤りは全体を滑らかにずらす。
[](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_shape_from_polarization/09_index_and_mixing.png)
↑ 較正球で測れば 0.01 以内に入る([4])。
↑ The animation ―― 検光子を 0〜180° 回したときの明るさ。黒い蝶ネクタイが回る —— 暗くなる向きが、その画素の面の傾きの向き(鏡面では入射面に直交)。
py -3.11 examples/poc_shape_from_polarization.py
Source: examples/poc_shape_from_polarization.py
This run produced 10 figures in total - see them all
Ops used (notes): aolp_to_azimuth · brdf_blinn_phong · dolp_to_zenith · fresnel_dielectric · photometric_stereo_robust · polarization_demosaic · sfp_disambiguate · sfp_dolp_model · sfp_estimate_index · sfp_height · sfp_normals · sfp_render
Thermography is a single track on bare In718; CT is Overhang Part X4 from a separate experiment and build, so no linkage is claimed. Recorded values: 4095 DL is 1401 C at emissivity 1 and 1641 C at 0.3. CT/design Dice is 0.981; protrusion is 63 micrometres down-facing versus 32 up-facing, but only 1.16 times at comparable slope. No internal void was found above about 63 micrometres.
↑ Metal-AM Thermography and X-ray CT — Separate Experiments ―― Thermography is a single track on bare In718; CT is Overhang Part X4 from a separate experiment and build, so no linkage is claimed. Recorded values: 4095 DL is 1401 C at emissivity 1 and 1641 C at 0.3. CT/design Dice is 0.981; protrusion is 63 micrometres down-facing versus 32 up-facing, but only 1.16 times at comparable slope. No internal void was found above about 63 micrometres.
↑ The measurement ―― calibration T = 14388/(a ln(c eps/x + 1)) - b/a (reading of the file’s model string is our assumption). 4095 DL = 1402 C at eps=1, 1641 C at eps=0.3. Pixels >= 2759 DL are above the liquidus for any eps <= 1. Source: National Institute of Standards and Technology (NIST), doi:10.18434/mds2-2716 (thermography) and doi:10.18434/mds2-2291 (XCT, measured at Georgia Tech); subsets extracted 2026-10-02, thresholded/cropped/false-coloured here (modified). Provided AS IS, https://www.nist.gov/open/license (figure labels are in Japanese; the numbers are the same)
↑ Line_0_1, row 300: 19 saturated frames are only a lower bound; the curve stops at frame 189 where the camera reports 0 (below 100 DL) instead of falli…
↑ Y pad: the command (XYPT, no time step stored) and the staring camera agree on the count and on the long last interval, yet drift apart by 2.3 % per p…
↑ slice at z = 4.00 mm just below the crown of the 4 mm hole: particles hang into the hole from the down-facing surface; red = design.
↑ XCT vs STL: protrusion = p95 of the signed distance (outward positive) after subtracting the median of each 0.25 mm surface cell (form error); line =…
↑ The animation ―― XCT slices 121..320 (z = 1.75..4.16 mm, build direction) with the STL cross-section in red. Note the powder/dross hanging under the hole crown and the 45 deg notch. Source: National Institute of Standards and Technology (NIST), doi:10.18434/mds2-2716 (thermography) and doi:10.18434/mds2-2291 (XCT, measured at Georgia Tech); subsets extracted 2026-10-02, thresholded/cropped/false-coloured here (modified). Provided AS IS, https://www.nist.gov/open/license
py -3.11 examples/poc_am_thermal_to_ct.py
Source: examples/poc_am_thermal_to_ct.py
This run produced 9 figures in total - see them all
Ops used (notes): blob_features · blob_label · blob_select_largest · median · mesh_slice_stack · seg_boundary_f · seg_dice_jaccard · signed_surface_distance · sk_otsu · vol_boundary_points · vol_gaussian · vol_label · vol_region_props
SEM slope contrast and a finite PSF (point spread function) broaden a step response. The second-moment width of the response itself matches the PSF, but that is not the effective edge displacement after thresholding. In the synthetic run, median Dice is 0.942 and area ratios span 0.755–1.698. The median relative difference between measured edge displacement and the closed form w = −sigma Phi^-1((t−1)/(sec theta−1)) is 0.138 / 0.260 / 0.653 at threshold/base 1.02 / 1.05 / 1.10 (0.260 overall), so this is not described as close agreement. The value 1.01 for effective width/PSF is specifically the median at threshold/base 1.02. Threshold-specific correction leaves median bias 0.035 / 0.030 / 0.033 at 1.02 / 1.05 / 1.10. At dose 45 e-/px, threshold 1.02 gives area ratios 8.42–8.74, where the noiseless correction is inapplicable. The real-data threshold was changed from one derived from ground-truth mask area to one using only background median and robust spread. That Carinthia-S stage runs only when its environment variable is set: the former Dice values 0.51 / 0.70 / 0.79 came from the old threshold and are not results of the revised method; revised real-data values were not available in this environment. This numerical model does not establish that subtracting a known PSF alone generally removes bias.
↑ How Much the Bright SEM Edge Inflates a Dimension — the Shape Matches but the Area Grows ―― The lead figure puts two electron-image contrasts side by side and overlays truth, detections and edge bias. Ten times the dose gives 3.16x SNR; edge-area bias grows toward small defects.
Terms: PSF — The spatial response of an imaging system to a point object, describing how that point is blurred in the image.
↑ The measurement ―― 0.5 nm/pixel で再計算した 256 nm 視野。明帯幅は PSF とともに増える。 (figure labels are in Japanese; the numbers are the same)
↑ 低照射量ほど粒状のショット雑音が増える。
↑ 合成 SEM 像(半径 22 px の円錐欠陥、PSF σ=3)。
↑ 面積の太りは小さい欠陥ほど大きい(周囲長/面積 ∝ 1/R)。
py -3.11 examples/poc_sem_wafer_defect_edge_bias.py
Source: examples/poc_sem_wafer_defect_edge_bias.py
This run produced 5 figures in total - see them all
Ops used (notes): blob_features · blob_label · pod_hitmiss_fit · seg_boundary_f · seg_dice_jaccard · sem_image · sem_render
How much of a km-scale Michelson interferometer reading a strain of 1e-21 can be reproduced in closed form, scored on the public GW150914 data (GWOSC) and the published numbers. Twelve new ops: the arm cavity (fabry_perot_response gives finesse 445 and a 42 Hz pole from the public aLIGO design values, cavity_strain_transfer, michelson_dc_readout, pdh_error_signal with the slope of Black 2001 to 0.9999), the quantum-noise budget (quantum_noise_budget: shot x radiation pressure = SQL^2/2 as an identity, and the second implementation gwinc 0.6.2 agrees within 0.96 to 1.12 at eight frequencies) and the signal side (psd_welch -> whiten_by_psd -> matched_filter_snr / optimal_snr, chirp_newtonian, chirp_mass_from_track, coincidence_delay). On the real data, whitening plus a 35-350 Hz band correlates at 0.959 / 0.935 with the published Fig. 1 of PRL 116, 061102; the matched filter with the numerical-relativity waveform gives SNR 19.0 (H1) / 13.1 (L1) against the published ~20 / 13, a network SNR of 23.1 against 24, and L1 leading by 7.1 ms against 6.9 ms. The Newtonian chirp reaches only 9.1 / 5.6 (no merger or ringdown). The ratio to the design curve (T1800044) is 0.42 at 20 Hz, 1.99 at 100 Hz and 0.33 at 1 kHz, which the table attributes to signal recycling and thermal noise. The take-home for inspection: a known waveform in 1/f^2 coloured noise is recovered by the whitened matched filter at the injected height, while plain correlation keeps 0.98 for a narrowband tone but only 0.50 for a chirp and 0.37 for a ring-down. The real-data stage runs when the files are placed and says ‘not placed’ otherwise. Nothing is learned, nothing is calibrated.
↑ A Pseudo-Simulation of a Gravitational-Wave Detector — Cavity Optics, the Quantum-Noise Budget and the Matched Filter in Closed Form, Scored on the Real GW150914 Data ―― How much of a km-scale Michelson interferometer reading a strain of 1e-21 can be reproduced in closed form, scored on the public GW150914 data (GWOSC) and the published numbers. Twelve new ops: the arm cavity (fabry_perot_response gives finesse 445 and a 42 Hz pole from the public aLIGO design values, cavity_strain_transfer, michelson_dc_readout, pdh_error_signal with the slope of Black 2001 to 0.9999), the quantum-noise budget (quantum_noise_budget: shot x radiation pressure = SQL^2/2 as an identity, and the second implementation gwinc 0.6.2 agrees within 0.96 to 1.12 at eight frequencies) and the signal side (psd_welch -> whiten_by_psd -> matched_filter_snr / optimal_snr, chirp_newtonian, chirp_mass_from_track, coincidence_delay). On the real data, whitening plus a 35-350 Hz band correlates at 0.959 / 0.935 with the published Fig. 1 of PRL 116, 061102; the matched filter with the numerical-relativity waveform gives SNR 19.0 (H1) / 13.1 (L1) against the published ~20 / 13, a network SNR of 23.1 against 24, and L1 leading by 7.1 ms against 6.9 ms. The Newtonian chirp reaches only 9.1 / 5.6 (no merger or ringdown). The ratio to the design curve (T1800044) is 0.42 at 20 Hz, 1.99 at 100 Hz and 0.33 at 1 kHz, which the table attributes to signal recycling and thermal noise. The take-home for inspection: a known waveform in 1/f^2 coloured noise is recovered by the whitened matched filter at the injected height, while plain correlation keeps 0.98 for a narrowband tone but only 0.50 for a chirp and 0.37 for a ring-down. The real-data stage runs when the files are placed and says ‘not placed’ otherwise. Nothing is learned, nothing is calibrated.
↑ The measurement ―― 単極 1/√(1+(f/f_p)²) × 往復伝搬の sinc(2πfL/c)。極で 1/√2、FSR = c/2L で零。 (figure labels are in Japanese; the numbers are the same)
↑ 反対称で共振で零。
↑ 通常干渉計(信号再利用なし)の量子雑音。
↑ 数値相対論の波形は公表どおりの山を立て、ニュートン近似(合体・リングダウン無し)は半分以下。
↑ 雑音が 1/f² のとき、波形が低周波を含むほど素の相関は損をし、白色化した整合フィルタは最適 SNR どおりに拾う。
↑ The animation ―― 入射光量 100 W → 100 kW: 散射雑音は 1/√P で下がり、輻射圧雑音は √P で上がる。総和は K = 1 の周波数で SQL に触れ、その点が光量とともに高い周波数へ動く。
FULLSEYE_GWOSC_DATA=<dir> py -3.11 examples/poc_gw_detector_pseudo_simulation.py
Source: examples/poc_gw_detector_pseudo_simulation.py
This run produced 7 figures in total - see them all
Ops used (notes): bandpass · cavity_strain_transfer · chirp_mass_from_track · chirp_newtonian · coincidence_delay · fabry_perot_response · matched_filter_snr · michelson_dc_readout · optimal_snr · pdh_error_signal · psd_welch · quantum_noise_budget · spectrogram · whiten_by_psd
A very shallow dent in glossy paint is practically invisible in an ordinary photograph. Projecting fringes on a screen in front of the surface and reading how their mirror image bends gives curvature, and a dent 3 µm deep stands out clearly. The flat-looking photo sits next to curvature maps in which the dents appear.
↑ Finding Micrometre Dents a Photograph Cannot Show, in the Mirror Image of a Fringe Screen — Phase-Measuring Deflectometry ―― A dent a few micrometres deep in a glossy surface (a painted car panel, a cover glass) is all but invisible in a lit photograph, because a mirror only shows the light. Put a screen in front of the surface, show phase-shifted fringes and photograph their mirror image: a tilt of 1e-3 rad moves the reflected point 0.6 mm on a screen 30 cm away. Six new ops (deflectometry, category deflectometry of opsspecular): the rig, the screen pattern, the forward render (height field to fringe mirror image, with a diffuse fraction, scatter, the back reflection of glass, aperture-integrated focus and noise), phase to normals (via match3d.normal_from_reflection), slope to curvature (one derivative, no integration) and the closed-form sensitivity. On a painted panel with 3 µm and 1 µm dents, 0.8 µm waviness and 0.12 µm polishing marks the photograph’s defect signal peaks at 0.00066 (0.17 of one 8-bit step) while the curvature map shows the dents at SNR 110 and 57, and the integrated depth is within 0.5 % of the truth. Phase noise matches sqrt(2/N) sigma/b at 1.00 and slope noise is 0.67 of sigma_u/(2d), the sqrt(4/9) of bilinear resampling. Where it breaks: lifting the part by 0.3 mm adds the apparent tilt dz sin(theta)/d (to 0.1 %) and a 388 m bowl while the dent depth moves 0.7 %; a 2 % screen-distance error makes dents 2.3 % shallower; a 1 µm dent survives a diffuse fraction of 0.85 and fringe orders start to jump at 0.9; on uncoated cover glass the back-surface light is bent the other way by the dent’s wedge, so the slope sensitivity shrinks to the closed-form 0.246 (measured 0.246) and dents look a quarter as deep; with an 8 mm aperture, focusing on the surface leaves 0.60 of the fringe amplitude and focusing on the screen leaves 0.52 of the depth. Nothing learned, nothing calibrated, 22 gates.
↑ The measurement ―― 面の前の画面(520 × 320 mm)にこの 4 枚を順に出し、面に映った鏡像を撮る。縦縞は画面の u(入射面内)を、同じ 4 枚を横縞にしたものが v を符号化する。粗い縞(周期 160 mm)の 4 枚で次数を決める。 (figure labels are in Japanese; the numbers are the same)
↑ 左が真値の高さ(視野 40 mm、青 = 高い・橙 = 低い)。
↑ 真値(破線)と復元(実線)はほぼ重なるので、差を 10 倍にして点線で重ねた。
↑ 平らな板を 0.3 mm 浮かせただけで、測った高さは 40 mm で約 14 µm 傾く(左、視野の中心で 3.4e-4 rad)。
↑ 表と裏の反射率はほぼ同じ(R1 0.043、R2 0.039)なので、2 つの縞の和の位相は上限 1.16 rad までずれ得る。
↑ The animation ―― 拡散成分 k_d = 0, 0.5, 0.8, 0.85, 0.9, 0.95, 0.98, 0.99, 0.995 と上げていったときの平均曲率(±2.5e-3 /mm)。雑音が 1/(1 − k_d) で増え、浅い凹み B が先に沈み、最後は粗い縞の次数が飛ぶ。
py -3.11 examples/poc_phase_measuring_deflectometry.py
Source: examples/poc_phase_measuring_deflectometry.py
This run produced 14 figures in total - see them all
Ops used (notes): absolute_phase · deflectometry_phase_to_slope · deflectometry_render · deflectometry_rig · deflectometry_screen_pattern · deflectometry_sensitivity · fresnel_dielectric · integrate · sfp_height · slope_to_curvature · text_box · wrapped_phase
Moving material (film, steel strip, wafers) is imaged with line sensors, and on a dark line TDI (time-delay integration) adds N stages of exposure. Six new ops (linescan, category linescan of opsoptics): TDI SNR from the EMVA 1288 linear model (charge-domain and digital summation), the closed-form MTF of a synchronisation error and of yaw, an acquisition that transports a reflectance map past the sensor (speed error, yaw, weave, encoder sync, lens, noise), a ray-traced profile of illumination and MTF across the image height, per-column lens maps and two example lenses. On a dark cold-rolled steel strip at 40 photons per stage, the SNR measured from the temporal noise of image pairs matches equation 21 within 1.2 % at every stage count. Digital summation gains exactly √N (×8.09 at 64 stages), charge summation pays the read noise once and gains ×26.9. Detection of a faint scratch, pinholes and an inclusion goes from 0 of 3 at one stage to 3 of 3 at 64 stages with no false alarms. A speed error ε smears the image by N·ε pixels and the scan MTF follows |
sinc(f N ε) | , printed first and then measured on a sine chart: largest difference 0.0035 over 45 conditions. A ±3 % speed ripple drops the local MTF to 0.64 when free-running and is fully removed by encoder sync; the cross-direction blur of yaw, | sinc(f N sin θ) | , is not (largest difference 0.0017). The same triplet optimised for a 57 mm line (f 90) and for a 2/3-inch format (f 35, a C-mount lens reused) was ray-traced: illumination splits exactly into cos⁴ × pupil × distortion (2e-16), the line-scan lens keeps 0.976 at the end of the sensor (cos⁴ alone gives 0.855; barrel distortion of −2.9 % adds ×1.111), and the C-mount lens delivers no light beyond 19.7 mm. Flat-field correction evens the brightness (0.961 to 1.000) but leaves the sine modulation unchanged (difference 0.0006), and the CNR of an inclusion at 14 mm stays at 1.52 to 1.54 with the C-mount lens against 2.49 with the line-scan lens. Nothing learned, nothing calibrated, 19 gates. |
*↑ TDI (time-delay integration) Line Scan and Line-Scan Lenses — Stages Reveal the Defects, a Sync Error Smears Them, and a C-Mount Lens Loses the Ends of a Long Sensor ―― Moving material (film, steel strip, wafers) is imaged with line sensors, and on a dark line TDI (time-delay integration) adds N stages of exposure. Six new ops (linescan, category linescan of opsoptics): TDI SNR from the EMVA 1288 linear model (charge-domain and digital summation), the closed-form MTF of a synchronisation error and of yaw, an acquisition that transports a reflectance map past the sensor (speed error, yaw, weave, encoder sync, lens, noise), a ray-traced profile of illumination and MTF across the image height, per-column lens maps and two example lenses. On a dark cold-rolled steel strip at 40 photons per stage, the SNR measured from the temporal noise of image pairs matches equation 21 within 1.2 % at every stage count. Digital summation gains exactly √N (×8.09 at 64 stages), charge summation pays the read noise once and gains ×26.9. Detection of a faint scratch, pinholes and an inclusion goes from 0 of 3 at one stage to 3 of 3 at 64 stages with no false alarms. A speed error ε smears the image by N·ε pixels and the scan MTF follows |
sinc(f N ε) | , printed first and then measured on a sine chart: largest difference 0.0035 over 45 conditions. A ±3 % speed ripple drops the local MTF to 0.64 when free-running and is fully removed by encoder sync; the cross-direction blur of yaw, | sinc(f N sin θ) | , is not (largest difference 0.0017). The same triplet optimised for a 57 mm line (f 90) and for a 2/3-inch format (f 35, a C-mount lens reused) was ray-traced: illumination splits exactly into cos⁴ × pupil × distortion (2e-16), the line-scan lens keeps 0.976 at the end of the sensor (cos⁴ alone gives 0.855; barrel distortion of −2.9 % adds ×1.111), and the C-mount lens delivers no light beyond 19.7 mm. Flat-field correction evens the brightness (0.961 to 1.000) but leaves the sine modulation unchanged (difference 0.0006), and the CNR of an inclusion at 14 mm stays at 1.52 to 1.54 with the C-mount lens against 2.49 with the line-scan lens. Nothing learned, nothing calibrated, 19 gates.* |
Terms: TDI — A line-sensor technique that sums charge or signals over multiple stages in step with image motion to increase exposure and SNR.; EMVA 1288 — A standard measurement procedure and set of quantities for sensitivity, noise, saturation, and non-uniformity of machine-vision image sensors.; MTF — The magnitude of the optical transfer function, showing how image contrast is transferred at each spatial frequency.
↑ The measurement ―― EMVA 1288 の 2 枚撮り(時間雑音)で測った SNR と、式 21 を段数倍した閉形式。 (figure labels are in Japanese; the numbers are the same)
↑ ε は搬送速度とラインレートの相対誤差。
↑ web が走査軸から θ 傾くと、64 段の間に幅方向へ 64·sin θ 画素ずれる。
↑ 光線追跡(射影立体角の一様格子)で数えた照度。
↑ 同じ明るさの尺度で表示。
py -3.11 examples/poc_tdi_linescan.py
Source: examples/poc_tdi_linescan.py
This run produced 10 figures in total - see them all
Ops used (notes): emva_pair_statistics · lens_field_profile · lens_shading_correct · lens_shading_gain · lens_system · linescan_acquire · linescan_lens_columns · linescan_lens_example · paraxial_trace · relative_illumination · tdi_snr · tdi_sync_mtf
The lead chapter runs on real raw data: four pixel-shift frames from a 35 mm full-frame mirrorless camera with sensor shift (PixelShift200, Qian et al., ICCP 2022, CC BY 4.0) of a resolution chart. The camera cycles (0,0)→(1,0)→(1,−1)→(0,−1); passing the op’s default order flags 23.2 % of the static scene as moving (0.09 % with the right order). The merge matches the authors’ own merged RGGB to a correlation of 0.99999996, false colour on the achromatic chart drops from 0.054 (single frame, bilinear) to 0.025, and bars finer than 0.25 cyc/px keep 1.67 times the contrast; a good demosaic (AHD in rawpy) comes within 1 % of the four-frame contrast with 0.020 colour, because the chart’s finest bars stop at 0.31 cyc/px. The synthetic chapters that follow sweep the failure conditions. What pixel shift (moving the sensor by one or half a pixel with a piezo actuator) adds to visual inspection and what it does not. Six new ops (pixelshift, category pixelshift of opsoptics): shift sequences with actuator error, an exact box-aperture capture through a shifted Bayer or monochrome sensor, a grid merge with the nominal shifts that refuses sequences that cannot fill the grid, a motion mask, a false-colour metric and the closed-form budget (sinc times diffraction MTF, Q = λN/p). Four 1-pixel shifts take false colour from 0.043-0.084 to zero on PCB traces, cloth, halftone print and a resolution chart (zero by construction when static and exact: R, G and B at every pixel see the same point through the same aperture). Half-pixel shifts move the FFT peak of 0.6-0.9 cyc/px gratings from the alias 1 − f₀ back to f₀, with heights matching 0.4·sinc(f₀) to 2e-14. The slanted-edge MTF50 is 0.478 single, 0.475 shifted and 0.467 cyc/px closed form: shifting removes aliasing, not MTF loss. Drizzle, an independent implementation, puts the peak at 0.700, and at 0.300 if handed the sensor shifts unchanged, because its sign convention is the opposite. With 3.45 µm pixels and f/1.4 to f/22 the 0.7 cyc/px modulation follows sinc times the diffraction MTF to 0.0008, vanishes beyond Q = 1.43, and the resolution gain ends at Q* = 1.47, before the aliasing boundary Q = 2. A 0.5 fill factor gives 3.2 times the modulation at 0.8 cyc/px. Failure modes: a single frame off by 0.05 px gives the colour error 2πf·a·sinc·δ/√2 to 1.2 %; random error of 0.1 px gives false colour 0.012 against 0.053 for a single demosaic. A moving part at 0.25 px per frame still beats a single demosaic (0.161 against 0.205), but from 0.5 px per frame the merge is worse; falling back to one frame where the greens disagree reaches the single-frame ceiling with no false detections on static areas. Noise falls as σ/√c for R and B and σ/√(2c) for G within 2.1 %, but on a moving grating more frames mean more error (0.081 to 0.273 from 4 to 16 frames). Nothing learned, nothing calibrated, 34 gates.
↑ Pixel-Shift Imaging — Shift the Sensor One Pixel and False Colour Vanishes, Half a Pixel and Aliasing Vanishes; the MTF (modulation transfer function) Does Not Rise ―― The lead chapter runs on real raw data: four pixel-shift frames from a 35 mm full-frame mirrorless camera with sensor shift (PixelShift200, Qian et al., ICCP 2022, CC BY 4.0) of a resolution chart. The camera cycles (0,0)→(1,0)→(1,−1)→(0,−1); passing the op’s default order flags 23.2 % of the static scene as moving (0.09 % with the right order). The merge matches the authors’ own merged RGGB to a correlation of 0.99999996, false colour on the achromatic chart drops from 0.054 (single frame, bilinear) to 0.025, and bars finer than 0.25 cyc/px keep 1.67 times the contrast; a good demosaic (AHD in rawpy) comes within 1 % of the four-frame contrast with 0.020 colour, because the chart’s finest bars stop at 0.31 cyc/px. The synthetic chapters that follow sweep the failure conditions. What pixel shift (moving the sensor by one or half a pixel with a piezo actuator) adds to visual inspection and what it does not. Six new ops (pixelshift, category pixelshift of opsoptics): shift sequences with actuator error, an exact box-aperture capture through a shifted Bayer or monochrome sensor, a grid merge with the nominal shifts that refuses sequences that cannot fill the grid, a motion mask, a false-colour metric and the closed-form budget (sinc times diffraction MTF, Q = λN/p). Four 1-pixel shifts take false colour from 0.043-0.084 to zero on PCB traces, cloth, halftone print and a resolution chart (zero by construction when static and exact: R, G and B at every pixel see the same point through the same aperture). Half-pixel shifts move the FFT peak of 0.6-0.9 cyc/px gratings from the alias 1 − f₀ back to f₀, with heights matching 0.4·sinc(f₀) to 2e-14. The slanted-edge MTF50 is 0.478 single, 0.475 shifted and 0.467 cyc/px closed form: shifting removes aliasing, not MTF loss. Drizzle, an independent implementation, puts the peak at 0.700, and at 0.300 if handed the sensor shifts unchanged, because its sign convention is the opposite. With 3.45 µm pixels and f/1.4 to f/22 the 0.7 cyc/px modulation follows sinc times the diffraction MTF to 0.0008, vanishes beyond Q = 1.43, and the resolution gain ends at Q = 1.47, before the aliasing boundary Q = 2. A 0.5 fill factor gives 3.2 times the modulation at 0.8 cyc/px. Failure modes: a single frame off by 0.05 px gives the colour error 2πf·a·sinc·δ/√2 to 1.2 %; random error of 0.1 px gives false colour 0.012 against 0.053 for a single demosaic. A moving part at 0.25 px per frame still beats a single demosaic (0.161 against 0.205), but from 0.5 px per frame the merge is worse; falling back to one frame where the greens disagree reaches the single-frame ceiling with no false detections on static areas. Noise falls as σ/√c for R and B and σ/√(2c) for G within 2.1 %, but on a moving grating more frames mean more error (0.081 to 0.273 from 4 to 16 frames). Nothing learned, nothing calibrated, 34 gates.*
Terms: MTF — The magnitude of the optical transfer function, showing how image contrast is transferred at each spatial frequency.
↑ The measurement ―― 基板の配線・織物・網点・解像チャート(上から)。左 = 3 色を各画素で取る理想センサ、中 = Bayer 1 枚 + 双線形デモザイク、右 = 1 画素シフト 4 枚。44 × 44 画素を最近傍で 5 倍。静止・誤差 0 では 4 枚の偽色は定義上 0(4・5 章が壊れる条件)。 (figure labels are in Japanese; the numbers are the same)
↑ 解像チャートの 星形チャート(48 × 48 画素を 6 倍)。
↑ 左は画素 1 つ = 8 × 8、右は出力画素 1 つ(= ½ センサ画素)= 4 × 4 で表示。
↑ 画素 3.45 µm、λ 0.55 µm で F 1.4〜22。
↑ 4 枚のピクセルシフトは雑音を下げるためのものではない(R・B は 1 枚と同じ)。
↑ The animation ―― 左 = その枚の Bayer の生の値(画素ごとにフィルタの色で表示、センサの位置へずらして描く)、右 = 合わせた格子に R・G・B が揃っていく様子。最後は 1 枚のデモザイクと 4 枚の合成(24 × 24 画素を 10 倍)。
py -3.11 examples/poc_pixel_shift.py
Source: examples/poc_pixel_shift.py
This run produced 15 figures in total - see them all
Ops used (notes): airy_pattern · annotate_figure_grid · drizzle_resample · edge_spread · false_color_metric · halftone_screen · mtf50 · mtf_diffraction · pixel_shift_capture · pixel_shift_merge · pixel_shift_motion_mask · pixel_shift_pattern · pixel_shift_resolution_budget · psnr · raw_demosaic_bilinear · reflect · sfr_from_edge · text_box · weave_normals
Continuous-wave indirect time of flight (modulated light, correlation sampled at four phase steps) taken from raw buckets to depth and into its failure modes. Eight new ops (itof, category itof of opsoptics): a capture model with shot noise, ambient light, extra paths and pixel-aperture mixing; phase, amplitude and offset; the closed-form noise budget; multi-frequency unwrapping by exhaustive maximum-likelihood hypotheses, residue_crt or the two-frequency beat; a wrong-order probability bound; two-path separation by matrix pencil; a flying-pixel mask; and cyclic-error correction. Depth noise matches c/(4πf)·√((B+σ_r²)/2)/A to 0.999-1.029 over 11 conditions with SNR ≥ 3, and Lange’s formula exactly. 16/80/120 MHz is unambiguous to 18.7 m; the wrong-order rate is 0.116 at σ_φ = 0.33 rad (union bound 0.132). On the same raw signal residue_crt gives 0.1161, on par with maximum likelihood, because it now picks its anchor the Wang-Xia way (the band with the smallest noise variance); anchoring on 16 MHz, as before the fix, gave 0.556. With 80/100/120 MHz, shelves beyond 7.5 m silently fold to near range. A box under packaging film is pulled forward at one frequency (matching tof_multipath_phase); an 8-frequency matrix pencil (20-160 MHz) recovers every pixel to 2 cm at 0.8 m separation but nothing at 0.2 m or less. A room corner reads 8-99 mm too far everywhere, matching the one-bounce transport to 1e-15 m. The mask flags 97 % of the pixels floating off box edges and removes 0.03 % of step-free pixels. The ±84.8 mm square-wave cyclic error matches atan(u/(1−u)) and inverts to the 2.1 mm noise floor. Sunlight at 50 times the signal raises far wrong-order errors from 12 % to 93 %. Direct ToF on the same tabletop neither floats edges nor shifts under the film. On public raw sensor data (3 frequencies × 3 steps; Lawin, Forssén, Ovrén, ECCV 2016; Zenodo; CC BY 4.0) jumps over 30 cm are 0.131 for maximum likelihood and 0.131 for residue_crt (10-frame average; 0.293 before the anchor fix); discarding low-margin pixels keeps 68 % at 0.020. Nothing learned; calibrated only by 16 rail points and the left half of the real data; 30 gates.
↑ Indirect Time of Flight, Multi-Frequency — Higher Frequencies Measure Finer, but Wrapping, Multipath, Flying Pixels and Harmonics Break It ―― Continuous-wave indirect time of flight (modulated light, correlation sampled at four phase steps) taken from raw buckets to depth and into its failure modes. Eight new ops (itof, category itof of opsoptics): a capture model with shot noise, ambient light, extra paths and pixel-aperture mixing; phase, amplitude and offset; the closed-form noise budget; multi-frequency unwrapping by exhaustive maximum-likelihood hypotheses, residue_crt or the two-frequency beat; a wrong-order probability bound; two-path separation by matrix pencil; a flying-pixel mask; and cyclic-error correction. Depth noise matches c/(4πf)·√((B+σ_r²)/2)/A to 0.999-1.029 over 11 conditions with SNR ≥ 3, and Lange’s formula exactly. 16/80/120 MHz is unambiguous to 18.7 m; the wrong-order rate is 0.116 at σ_φ = 0.33 rad (union bound 0.132). On the same raw signal residue_crt gives 0.1161, on par with maximum likelihood, because it now picks its anchor the Wang-Xia way (the band with the smallest noise variance); anchoring on 16 MHz, as before the fix, gave 0.556. With 80/100/120 MHz, shelves beyond 7.5 m silently fold to near range. A box under packaging film is pulled forward at one frequency (matching tof_multipath_phase); an 8-frequency matrix pencil (20-160 MHz) recovers every pixel to 2 cm at 0.8 m separation but nothing at 0.2 m or less. A room corner reads 8-99 mm too far everywhere, matching the one-bounce transport to 1e-15 m. The mask flags 97 % of the pixels floating off box edges and removes 0.03 % of step-free pixels. The ±84.8 mm square-wave cyclic error matches atan(u/(1−u)) and inverts to the 2.1 mm noise floor. Sunlight at 50 times the signal raises far wrong-order errors from 12 % to 93 %. Direct ToF on the same tabletop neither floats edges nor shifts under the film. On public raw sensor data (3 frequencies × 3 steps; Lawin, Forssén, Ovrén, ECCV 2016; Zenodo; CC BY 4.0) jumps over 30 cm are 0.131 for maximum likelihood and 0.131 for residue_crt (10-frame average; 0.293 before the anchor fix); discarding low-margin pixels keeps 68 % at 0.020. Nothing learned; calibrated only by 16 rail points and the left half of the real data; 30 gates.
↑ The measurement ―― 平らな板(距離は一様乱数)で信号を振り、次数を誤った画素の割合を数えた。破線は候補ごとのガウスの 2 仮説の誤りの和(和集合の上界)、点線はその最大の 1 項。実測は 2 本の間に入る。 (figure labels are in Japanese; the numbers are the same)
↑ 通路の画素を距離帯に分け、次数を誤った割合と、その帯の位相雑音から見積もった上界を並べた。
↑ 向かいの壁で 1 回以上反射した光が遅れて混ざり、内角の面は全体に奥へ伸びる。
↑ 同じ卓上の場面を 2 つの方式で撮る。
↑ σ_d = c/(4πf)·√((B + σ_r²)/2)/A。
↑ The animation ―― 左の箱の点群を回して見る。横から見ると、縁から背後の床へ視線に沿って伸びる赤い点の列(フライングピクセル)が現れる。右は印を除いたもの。
py -3.11 examples/poc_itof_multifreq.py
Source: examples/poc_itof_multifreq.py
This run produced 14 figures in total - see them all
Ops used (notes): annotate_figure_grid · camera_rays · dtof_cube_depth · dtof_cube_simulate · erfc · form_factors_2d · gamma · gaussian · groove_profile · itof_capture · itof_flying_pixel_mask · itof_multipath_separate · itof_noise_budget · itof_phase_amplitude · itof_unwrap_error_rate · itof_unwrap_multifreq · itof_wiggling_correct · residue_crt · tof_multipath_phase · tof_transport_depth · transport_matrix_radiosity
We want to see the layers of paint and multilayer film in depth without cutting them. Clear-coat thicknesses computed from public raw interference spectra of automotive paint agree with the paper’s OCT values within 1.5 µm. Layers too thin to separate are reported as unmeasurable instead of guessed.
↑ Industrial OCT (optical coherence tomography) — Layer Thickness and Hidden Defects of Paints, Coatings and Multilayer Films, Seen in Depth by the FFT of One Spectral Interferogram ―― Optical coherence tomography sees the layer stack of paints, conformal coatings, adhesive layers and multilayer films, and voids and delaminations inside them, without cutting. Thirteen new ops (octscan, category oct of opsoptics): closed-form design numbers before measuring, interferogram simulators driven by the material table, k-linearisation, dispersion compensation and estimation, A- and B-scans, layer thickness through the group index (refusing layers that cannot be separated), boundary tracing, surface flattening and an entry point for raw spectra. Real data: public raw spectra of automotive paint panels (CC BY 4.0) processed with Fullseye ops alone give clear-coat thicknesses within 1.5 µm of the paper’s OCT and 3.6 µm of the microscope, base coats within 2.3 µm of the microscope for three of four panels (one is 7.6 µm off and unresolved), a refusal under the white base coat, and an autocorrelation ghost lying flat at the clear coat’s optical thickness. Synthetic: thickness through the group index within 0.04 µm of the truth, SNR, roll-off and dispersion broadening matching the closed forms, refusal below 1.4 point-spread widths, a coating-thickness map and void/delamination detection.
Terms: OCT — A non-contact method that uses low-coherence interference to measure depth-resolved reflectivity and form cross-sectional images.
↑ The measurement ―― 屈折率は論文と同じ 1.56(透明層)・1.64(着色層)。表のどの行がどのファイルかは論文に無く、透明層の厚さで決めた(推定)。着色層はその対応と独立の突き合わせ。 (figure labels are in Japanese; the numbers are the same)
↑ 黒・濃色は透明層の下の面と着色層の下の面の 2 つの山。
↑ SD 840/50 nm 2048 画素(深さ範囲 2.26 mm)、SS 1310/100 nm・瞬時線幅 0.1 nm。
↑ 試料腕が参照光と同じ桁に強いと、層の上下の面どうしの干渉が 0 の近くに立つ。
↑ 横 1.2 mm、縦は光路 25〜440 µm。
↑ The animation ―― 試料腕に溶融石英 12 mm の分散の不一致。補償の係数 a2 を 0 から 35.6 µm² まで動かすと、PMMA / PC / PS の 3 層の界面がにじみから線に締まる(横 0.6 mm、光路 130〜380 µm)。
py -3.11 examples/poc_industrial_oct.py
Source: examples/poc_industrial_oct.py
This run produced 15 figures in total - see them all
Ops used (notes): glass · material_optical_constants · oct_ascan · oct_bscan · oct_bscan_boundaries · oct_bscan_flatten · oct_bscan_simulate · oct_design · oct_dispersion_broadening · oct_dispersion_compensate · oct_dispersion_estimate · oct_from_spectra · oct_interferogram · oct_k_linearize · oct_layer_thickness
Oxide thickness on silicon can be read from interference fringes in reflected light. Scored on data published by other researchers, the measured reflectance of bare silicon matched the material table to 0.4 %. On films that are too thin, the fringe-counting method flags its own result as unreliable.
↑ Thin-Film Thickness by Spectral Reflectometry — Scored on Public Measured Data, then Fringes, Interference Colours, FFT versus Model Fit and Thickness Maps ―― Five ops (filmoptics, category film of opsoptics) measure the thickness of SiO₂ on silicon from the interference fringes of its reflectance; films and substrates are keys of the material table. The core is scoring on other people’s measured data (both CC BY 4.0). In the microspectroscopy of Schwarz et al. (2024) the measured reflectance of bare silicon agrees with the table’s silicon to 0.4 % (median); a normal-incidence fit gives 92.9 nm against the 98.5 nm reference (−5.7 %), and choosing the NA by the residual (0.5) gives 95.8 nm (−2.7 %); the coverage of the measurement spot is read back over 11 spectra with a mean error of 0.6 points. From the real colour micrograph of the same sample, a white balance on the bare silicon alone gives a per-pixel thickness map with a median of 87.4 nm (−11 %, camera sensitivities unknown). With the low-cost spectrometers of Sánchez-Arriaga et al. (2023) the 476.3 nm standard is within 2 % in all five spectra; the 198.7 nm standard reads a median of 6.6 % thick, and in one spectrum the best candidate is 51 nm while the right value is the second candidate. The FFT method flags the 98 nm film as unreliable (closed-form limit 554 nm). Synthetic chapters: Airy agreement 3e-15, fringe count within one, FFT limit 538 nm closed form versus 525 nm measured, closed-form biases for wrong index, angle and NA, 59 % hue agreement with the published colour chart in the first three orders (a weak gate), and a modelled wafer (radial comet to the edge, edge bead, off-centre rings, each tied to the literature or labelled a model) mapped within 0.17 nm RMS by a 31-band camera while 14 % of RGB pixels keep several candidates. Nothing learned, 37 gates.
↑ The measurement ―― 実測の反射スペクトルに film_thickness_fit を当てた。直入射のモデルは 92.9 nm、残差が最小の NA 0.5 で 95.8 nm。裸の Si は材料表の Si と中央値 -0.4 % 差。出典: Schwarz ら, Adv. Opt. Mater. (2024) doi:10.1002/adom.202402502、データ Zenodo 10.5281/zenodo.13753754(CC BY 4.0)。改変: 表から 2 本を選び、モデルの曲線を重ねた。 (figure labels are in Japanese; the numbers are the same)
↑ 476.3 nm は最大 1.2 %、198.7 nm は中央値 6.6 % 厚めに読み、1 本は最良の候補が 51 nm に外れた(正しい膜厚は 2 番目の候補)。
↑ 100 nm は縞が 1 本未満、2 µm は 7 本余り。
↑ 顕微分光の対物は斜めの光も入れる。
↑ 分光カメラは全画素で真値と RMS 0.17 nm。
↑ The animation ―― 膜厚を 0 → 1.5 µm に振ると縞が増え、色が周期的に巡りながら淡くなる。
py -3.11 examples/poc_thin_film_reflectometry.py
Source: examples/poc_thin_film_reflectometry.py
This run produced 13 figures in total - see them all
Ops used (notes): annotate_colorbar · blackbody_spectrum · film_color · film_thickness_fft · film_thickness_fit · film_thickness_map · multilayer_reflectance · spectrum · text_box · thinning
This numerical model builds a chromatic confocal sensor from a lens prescription and material dispersion. The 450–700 nm focus range is 1,191 um; paraxial focus and axial-response width match closed forms. Estimator biases are 0.060, 0.108, 0.293 and 2.04 um for Gaussian, parabolic, centroid and peak-bin methods; after synthetic moving-mirror calibration they are 0.014 and 0.090 um for Gaussian and parabolic fits. Plate readout uses apparent depth as seen from air and converts it back to physical thickness. After fixing an erroneous extra /n in first-layer spherical aberration and using an independent Snell-ray expression, the 500 um borosilicate-crown plate is −1.81 um with paraxial correction and −0.053 um after NA correction; the maximum relative error over four plates including sapphire and PMMA is 7.75e-4. The pupil-overlap formula differs from Monte Carlo by at most 0.0011. Dividing by gold reflectance or a white reference uses the same curve used in synthesis and is therefore an ideal upper bound, not validation of an instrument calibration. Values read from the public paper figures, including 66.095 um, are recorded values from a stage that runs only when its environment variable is provided and were not checked in this run. The numerical model alone does not establish real-instrument plate-correction accuracy.
↑ Chromatic Confocal Displacement Sensor — A Rainbow Ruler from Glass Dispersion, Reading Height from the Colour that Comes Back ―― This numerical model builds a chromatic confocal sensor from a lens prescription and material dispersion. The 450–700 nm focus range is 1,191 um; paraxial focus and axial-response width match closed forms. Estimator biases are 0.060, 0.108, 0.293 and 2.04 um for Gaussian, parabolic, centroid and peak-bin methods; after synthetic moving-mirror calibration they are 0.014 and 0.090 um for Gaussian and parabolic fits. Plate readout uses apparent depth as seen from air and converts it back to physical thickness. After fixing an erroneous extra /n in first-layer spherical aberration and using an independent Snell-ray expression, the 500 um borosilicate-crown plate is −1.81 um with paraxial correction and −0.053 um after NA correction; the maximum relative error over four plates including sapphire and PMMA is 7.75e-4. The pupil-overlap formula differs from Monte Carlo by at most 0.0011. Dividing by gold reflectance or a white reference uses the same curve used in synthesis and is therefore an ideal upper bound, not validation of an instrument calibration. Values read from the public paper figures, including 66.095 um, are recorded values from a stage that runs only when its environment variable is provided and were not checked in this run. The numerical model alone does not establish real-instrument plate-correction accuracy.
↑ The measurement ―― 左: 単レンズの範囲 1191 µm、ハイパークロマート 1842 µm(焦点距離あたり 1.63 倍)。近軸焦点は閉形式と 4e-12 µm で一致(点)。右: 鏡で較正したピークは近軸焦点から縦球面収差のほぼ半分だけずれる —— 450 nm で -20.0 µm。575 nm では 0(非球面で消した色)。 (figure labels are in Japanese; the numbers are the same)
↑ 鏡面の応答は sinc²(u/2)、FWHM = 0.443 λ/(1 − cos α)(∝ λ/NA²)。
↑ 段差は 49.959 µm と読める(RMS 誤差 0.276 µm)。
↑ 左: 透明な板は表と裏の 2 ピーク。
↑ (a) 鏡面の傾きが 8.8°(sin 2θ = 2NA)を超えると何も戻らない(瞳の光線のモンテカルロと一致)。
↑ The animation ―― 上: 虹のものさし(焦点付近の光線)と面(灰色の縦線)。下: ピンホールへ戻るスペクトル(棒の色はその波長の色、ピーク 3000 光子 + 読出し雑音)。面を遠ざけると、ピントの合う色が青から赤へ移り、戻る色が変わる。ピーク波長を較正表で引くと高さになる。
py -3.11 examples/poc_chromatic_confocal.py
Source: examples/poc_chromatic_confocal.py
This run produced 8 figures in total - see them all
Ops used (notes): abbe_number · chromatic_confocal_limits · chromatic_confocal_peaks · chromatic_confocal_probe · chromatic_confocal_readout · chromatic_confocal_spectrum · cie_xyz_from_wavelength · confocal_axial_response · gaussian · led_spectrum · lens_system · material_optical_constants · material_reflectance · paraxial_trace · plate_focus_shift · text_box · trace_rays · with_wavelength
An SEM image is not a photo of the surface but a tally of electrons escaping after scattering inside the solid. A model that tracks each electron was checked against measured backscatter data for 7 elements. On the same line, the beam energy decides which sidewall height is read as the linewidth.
↑ A Virtual SEM — Fire Electrons One by One into the Solid, and the Image and the Measured Line Width Change with the Voltage (Scored against Measured Data) ―― An SEM image is not a photograph of the surface: it is a tally of what comes out of the interaction volume of primary electrons scattering inside the solid. Eight new ops (sem_electron_mc, sem_yield_curve, sem_se_universal_curve, sem_line_profile, sem_cd_measure, sem_image, sem_charging_series, sem_se_escape_fraction) and the material-table op material_electron_properties build a Monte Carlo that tracks electrons one by one (Joy’s single scattering, Browning’s 1994 Mott fit for elastic scattering, the Joy-Luo stopping power), and every stage is scored against outside measurements. The backscatter coefficient for 7 elements (C to Au) at 5/10/20/30 keV falls inside the measured bands of Joy’s database plus or minus 0.04; switching back to screened Rutherford fails for W and Au. The maximum penetration is 0.83 to 1.04 of the Kanaya-Okayama range and the depth grows as E^1.75 (Si) and E^1.52 (Au) against 1.67. The shape of the secondary-electron yield matches the Lin and Joy 2005 universal curve within RMS 0.076 for C to Ag, with Emax within -4 to +7 % of their table (not fitted); W and Au have heavier tails (SE2 dominates, unresolved). The user’s guess that W on Si is invisible in SE but stands out in backscatter Z contrast holds only for flush W at 1 kV (SE +5 %, BSE +96 %); at 5 kV SE shows it too (+80 %) because the SE2 made by exiting backscattered electrons carries Z. W buried 150 nm deep shows +3 % at 1 kV even in backscatter and +154 % at 20 kV (information depth). For CD-SEM, a PMMA line (40 nm at the base) with an 88 degree sidewall measures 39.1 nm at 1 kV and 39.9 nm at 20 kV by threshold: low voltage measures high on the sidewall (0.33), high voltage at the foot (0.03); at 80 degrees the difference is small and reversed, so this is no general rule. A vertical sidewall reads wide at every voltage (+0.3 to +1.0 nm). Charging: at 0.5 kV (eta + delta > 1) the insulator charges positive, darkens and stops at the potential that solves eta + delta F(V) = 1 by an independent bisection (to 2e-15 V); at 20 kV it keeps charging negative and brightens (phenomenological). A defect found on the way is kept on record: free paths were drawn in the starting material and walked straight through material boundaries, so a 0.01 nm Si cap changed W’s eta from 0.425 to 0.329 (now truncated at the boundary, with a regression gate). No real-image chapter yet: no CC BY CD-SEM images with ground-truth line widths were found.*
↑ The measurement ―― δ(E) を各元素の MC の山(Emax, δmax)で割った形(横軸は対数)。破線は Lin & Joy 2005 の普遍曲線(式 12)。C〜Ag の 5 元素は重なる。W と Au(赤)は E > 3 Emax で裾が厚い —— 重元素は反射電子が多く、表面を抜けるときに作る SE2 が主になるので、SE1 だけを仮定した普遍曲線からずれる(未解決のまま数で固定)。 (figure labels are in Japanese; the numbers are the same)
↑ レンズ内の SE の線プロファイル(基板の水準で割った)。
↑ PMMA の線(高さ 40 nm)を Si の上で。
↑ Si に埋もれた W(幅 100 nm、厚さ 60 nm)の、真上と 400 nm 離れた Si の平均との比 − 1(反射電子 / レンズ内 SE)。
↑ 各パネルは Si の水準で割った信号(0〜3 で共通の濃淡)。
↑ The animation ―― Si の上の PMMA の線を 5 kV で 24 コマ走査し続けた像。PMMA だけが負に帯電して明るくなり、接地した Si は変わらない。
py -3.11 examples/poc_virtual_sem_kv_cd_bse.py
Source: examples/poc_virtual_sem_kv_cd_bse.py
This run produced 9 figures in total - see them all
Ops used (notes): material_electron_properties · sem_cd_measure · sem_charging_series · sem_electron_mc · sem_image · sem_line_profile · sem_se_escape_fraction · sem_se_universal_curve · sem_yield_curve · threshold
In a transmission electron microscope, defocus decides whether fine detail looks bright or dark. Estimating defocus from 3 public real micrographs closely matched the authors’ own software. Sweeping focus moves the concentric Thon rings and flips detail near true focus.
↑ A Virtual Transmission Electron Microscope — Thon Rings (CTF rings in a TEM power spectrum) Move with Focus, Fine Detail Reverses, and Defocus Is Read from Real Micrographs ―― A TEM turns the phase of electrons that pass a thin specimen into intensity through the spherical aberration Cs and the defocus Δf. The transfer, CTF = −sin(χ + arcsin w), flips sign with spatial frequency and draws its zeros as concentric Thon rings in the power spectrum. Eleven new ops (temsim, category tem of opsoptics): electron wavelength, Scherzer defocus, 2-D and 1-D CTF, projected potential, amorphous film, weak-phase image, multislice, power spectrum, CTF estimation and correction. Imaging reuses coherent_image with the pupil e^{iχ}, electron scattering factors come from the X-ray factors of pxrd via Mott–Bethe, lomb_scargle seeds the ring period from samples uneven in k², and phase_correlation_fft aligns frames. Wavelengths match the published values to five digits (1.9687 pm at 300 kV; 13.7 % longer without relativity), CTF zeros match the closed form to 1.7e-12 1/Å, the first zero at Scherzer focus is 0.6580 Cs^¼ λ^¾, and a sinusoidal phase grating diffracts as J_n(φ₀)² to 1.6e-13. The mean inner potential of Si is 13.83 V against the neutral-atom 13.71 V (above the measured 12.48 V by the bonding share); Cu’s f_e(0) is 10.5 % low, a limit of the X-ray fit. The image best resembles the projected potential at 900 Å, within 7 % of Scherzer’s 842 Å, and fine detail reverses at exact focus (correlation −0.30). Four synthetic micrographs give Δf within 13 Å and astigmatism within 50 Å and 5.2°, with jackknife errors that cover the misses; noise only, too low a dose, near-focus and a fit trapped in a wrong valley with only nine tiles are all refused. A 2 % pixel-size error scales Δf by 1.039 (closed form 1.0404). Ten drifting frames lose their rings when summed (fit resolution 15.6 Å) and regain them when aligned (5.3 Å). Phase flipping raises the correlation with the potential from 0.09 to 0.84, Wiener to 0.87, and a 10 % defocus error drops it to 0.10. On three real micrographs (EMPIAR-10028, CC0) the estimate agrees with the authors’ CTFFIND3 values to −55, +65 and −0.5 Å (within 0.2 %). Nothing learned, nothing calibrated, 36 gates.
Terms: Thon rings — Concentric rings in a TEM image power spectrum that follow CTF zeros and sign changes and are used to estimate defocus and astigmatism.; CTF — In transmission electron microscopy, the function describing the sign and strength with which specimen spatial frequencies transfer to the image under aberration and defocus.
↑ The measurement ―― 焦点外れ Δf を 0.3 → 3 µm と振る(300 kV、Cs 2.7 mm、非晶質炭素 50 Å、40 e/Ų 相当の雑音)。左はパワースペクトル(k ≤ 0.25 Å⁻¹、動径の背景で割った)、右は同じ写真の一部。Thon リングは Δf とともに内側へ詰まり、写真の粒状の模様が細かくなる。 (figure labels are in Japanese; the numbers are the same)
↑ CTF = −sin(χ + arcsin w) に空間・時間の包絡を掛けた曲線(300 kV、Cs 2.7 mm、照明の半角 0.05 mrad、焦点の広がり 60 Å)。
↑ 真値 Δf 25000 Å・非点収差 1500 Å・75° の合成写真。
↑ 10 枚が 1 枚あたり (2.0, 0.6) 画素ずつドリフト。
↑ EMPIAR-10028 part1_001 の 1024 × 1024 画素(1.34 Å/画素)。
py -3.11 examples/poc_tem_ctf_thon_rings.py
Source: examples/poc_tem_ctf_thon_rings.py
This run produced 16 figures in total - see them all
Ops used (notes): tem_amorphous_film · tem_ctf · tem_ctf_correct · tem_ctf_estimate · tem_ctf_profile · tem_image_weak_phase · tem_multislice · tem_power_spectrum · tem_projected_potential · tem_scherzer · tem_wavelength
The hyperspectral PoC used a 10 nm band and the chromatic confocal PoC a spectrometer line width as given numbers. This exhibit builds those numbers from the entrance slit, the grating grooves, the mirror focal lengths and the detector pixels with the grating equation, and the unevenness of microscope and coaxial lighting from which plane of the source is imaged where. Eight new ops: in gratingspec (new category spectrometer of opsoptics) the grating equation with dispersion, resolving power mN and overlapping orders; the first-order Czerny-Turner design with Shafer’s coma condition; the bandpass as the FWHM of slit image, pixel and diffraction convolved, with the etendue; smile and keystone from the 3-D grating equation that keeps the direction cosine along the grooves; and the detector reading of an input spectrum with second- and third-order overlap. In illumdesign: the Koehler or critical illumination train, the specimen irradiance by paraxial backward tracing to the source, and the coaxial half mirror with its round-trip R·T and the image of a mirror-like or matte part. Against two published spectrometers (CC BY 4.0 papers) the Shafer angle comes out 14.9679° (published 14.968°), the camera focal length 68.88 mm (r = 136 mm), the slit-image widths 39.2 / 43.7 / 52.6 µm (40 / 43 / 50) and the resolution 0.397 nm (0.385 to 0.391); only the 0.4 nm centre resolution of Feng 2021 disagrees, the slit image alone giving 0.49 nm from the paper’s own values, and the gap is left open. Narrow slits stop at the diffraction floor 0.886 λ/(mN) (within 5e-4 for a rectangular pupil), wide slits keep light times resolution constant; the mercury pair at 576.96 / 579.07 nm splits with a 50 µm slit and merges into one false peak at 150 µm. A 4 mm slit image bows 41.9 µm (3 pixels) towards longer wavelengths, within 0.2 % of the closed form, and the Littrow curvature radius matches f/(2 tan β); a broken model that reuses the in-plane equation shows no smile and fails the gate. The 253.65 and 365.02 nm mercury lines appear in second order as false lines at 507.30 and 730.04 nm and vanish behind a 400 nm long-pass filter. For an InGaAs spectrometer at 900 to 1700 nm the hyperspectral 10 nm corresponds to a 75 µm slit (trapezoidal, not Gaussian). Widening the visible spectrometer slit of the chromatic confocal sensor merges the two surfaces of thin glass plates until the readout refuses them. Critical illumination copies a coiled filament into the field (CV 0.53), Koehler illumination removes it (CV 2e-16), and moving the aperture stop 4 mm off the condenser focal plane lets the stripes leak back. The coaxial round trip is R·T = 1/4 at 50/50, matching the optscene coaxial light. Nothing learned, nothing calibrated, 40 gates.
↑ Spectrometer and Illumination Trains — Where the Bandwidth Comes From, and Whether the Lamp Shows in the Field ―― The hyperspectral PoC used a 10 nm band and the chromatic confocal PoC a spectrometer line width as given numbers. This exhibit builds those numbers from the entrance slit, the grating grooves, the mirror focal lengths and the detector pixels with the grating equation, and the unevenness of microscope and coaxial lighting from which plane of the source is imaged where. Eight new ops: in gratingspec (new category spectrometer of opsoptics) the grating equation with dispersion, resolving power mN and overlapping orders; the first-order Czerny-Turner design with Shafer’s coma condition; the bandpass as the FWHM of slit image, pixel and diffraction convolved, with the etendue; smile and keystone from the 3-D grating equation that keeps the direction cosine along the grooves; and the detector reading of an input spectrum with second- and third-order overlap. In illumdesign: the Koehler or critical illumination train, the specimen irradiance by paraxial backward tracing to the source, and the coaxial half mirror with its round-trip R·T and the image of a mirror-like or matte part. Against two published spectrometers (CC BY 4.0 papers) the Shafer angle comes out 14.9679° (published 14.968°), the camera focal length 68.88 mm (r = 136 mm), the slit-image widths 39.2 / 43.7 / 52.6 µm (40 / 43 / 50) and the resolution 0.397 nm (0.385 to 0.391); only the 0.4 nm centre resolution of Feng 2021 disagrees, the slit image alone giving 0.49 nm from the paper’s own values, and the gap is left open. Narrow slits stop at the diffraction floor 0.886 λ/(mN) (within 5e-4 for a rectangular pupil), wide slits keep light times resolution constant; the mercury pair at 576.96 / 579.07 nm splits with a 50 µm slit and merges into one false peak at 150 µm. A 4 mm slit image bows 41.9 µm (3 pixels) towards longer wavelengths, within 0.2 % of the closed form, and the Littrow curvature radius matches f/(2 tan β); a broken model that reuses the in-plane equation shows no smile and fails the gate. The 253.65 and 365.02 nm mercury lines appear in second order as false lines at 507.30 and 730.04 nm and vanish behind a 400 nm long-pass filter. For an InGaAs spectrometer at 900 to 1700 nm the hyperspectral 10 nm corresponds to a 75 µm slit (trapezoidal, not Gaussian). Widening the visible spectrometer slit of the chromatic confocal sensor merges the two surfaces of thin glass plates until the readout refuses them. Critical illumination copies a coiled filament into the field (CV 0.53), Koehler illumination removes it (CV 2e-16), and moving the aperture stop 4 mm off the condenser focal plane lets the stripes leak back. The coaxial round trip is R·T = 1/4 at 50/50, matching the optscene coaxial light. Nothing learned, nothing calibrated, 40 gates.
↑ The measurement ―― スリットの幅で帯域の幅とエタンデュがどう動くか(1200 本/mm、M 型 2025 の設計、625 nm)。左: 細いスリットは回折の床 0.886 λ / (mN) で止まり、NA を上げて照らす溝を増やすと床が下がる。右: 広いスリットでは光の量と分解能の積が一定(傾き −1)、細くしていくと床に当たって光だけを失う。 (figure labels are in Japanese; the numbers are the same)
↑ Hg の黄色の 2 本を、スリットの幅を変えて読む。
↑ 1 次の 500〜750 nm の検出器に、紫外の Hg 線の 2 次の像が偽の線として乗る(効率 0.3 の模型)。
↑ 可視の分光器(600 本/mm)のスリットを広げると、薄い板の表裏の 2 ピークが 1 つに潰れ、readout は厚さを当て推量せず拒否する。
↑ 同軸落射(視野 44 mm 角): 鏡面の部品はパネルそのものを写す(R·T = 1/4)。
py -3.11 examples/poc_spectrometer_illumination.py
Source: examples/poc_spectrometer_illumination.py
This run produced 10 figures in total - see them all
Ops used (notes): chromatic_confocal_peaks · chromatic_confocal_probe · chromatic_confocal_readout · chromatic_confocal_spectrum · coaxial_illumination · czerny_turner_design · grating_dispersion · illumination_field · illumination_train · spectrometer_bandpass · spectrometer_slit_image · spectrometer_spectrum · text_box · wave_grating_orders
A grating interferometer’s dark field (the loss of fringe amplitude) shows what absorption hides: fibre orientation, a 2 µm crack and a cluster of pores. Eight new ops (gratinginterf, category grating_interf of opsoptics): closed-form design (fractional Talbot distances for π, π/2 and absorption gratings, the cone-beam distance d’ = Ld/(L − d), analyser and Lau source-grating periods), the Talbot carpet (the intensity I(x, z) behind the grating laid out along distance z, like a woven fringe pattern), computed as a sum over diffraction orders, the visibility loss from source size, Gaussian focal spots and source gratings, a phase-stepping forward model, estimation of the steps per period from the sine-fit residual, retrieval of transmission, differential phase and dark field from sample and reference stacks, dark field to scattering width, and integration of the differential phase into phase and thickness (refusing wrapped input). The four visibility maxima of the carpet sit on the fractional Talbot distances (m − 1/2)p²/(4λ) to within the 0.89 mm grid, at height 1.2732 (4/π for a square wave), and agree with an FFT propagation of the sampled field to 8e-4. A 2 mm focal spot washes the fringes out to x0.001; a source grating at the Lau period (41 slits) restores 2/π = 0.637, and detuning it by 1/41 kills them again (matching an incoherent point sum to 6e-5). In a short-fibre composite plate the 2 µm crack reaches CNR 13.5 in the dark field against 1.34 in absorption, the cos 2ω of four grating directions recovers fibre orientation to a median 2.7° (90th percentile 6.6°), and the weld line behind the hole reads 89.8° for 90°. Probe: with only 0° and 90° the diagonal fibres are off by 30.3°. Integrating the differential phase recovers a rod’s thickness to 1.0 %, and the op refuses once the true |
φ | exceeds π. Honest limit: a slab whose sharp edge pushes a single pixel past π wraps invisibly and the op silently returns -857 µm for 2000 µm. Assuming P = N for a scan of 20.85 steps per period leaves 0.063 rad of phase error, the estimated P 0.022 rad; dividing the residual by the fitted mean biases the minimum by +0.03 steps (the op divides by the raw mean). Real data (CC BY 4.0, Zenodo 16944391): 20.80 and 22.30 steps per period for the 22-frame reference and 24-frame object scans (residual 0.0073 to 0.0044); the strongly absorbing sphere scatters little (-ln D 0.17) while the fibre bundle scatters strongly (0.55), and the differential phase changes sign across the sphere. Nothing learned, nothing calibrated, 22 gates (with the real-data chapter run; 19 when the data is absent, as in CI). |
*↑ Grating Interferometry — Fibre Orientation, Micro-Cracks and Pores That Absorption Cannot See, Read From the Fringe Amplitude (Dark Field) ―― A grating interferometer’s dark field (the loss of fringe amplitude) shows what absorption hides: fibre orientation, a 2 µm crack and a cluster of pores. Eight new ops (gratinginterf, category grating_interf of opsoptics): closed-form design (fractional Talbot distances for π, π/2 and absorption gratings, the cone-beam distance d’ = Ld/(L − d), analyser and Lau source-grating periods), the Talbot carpet (the intensity I(x, z) behind the grating laid out along distance z, like a woven fringe pattern), computed as a sum over diffraction orders, the visibility loss from source size, Gaussian focal spots and source gratings, a phase-stepping forward model, estimation of the steps per period from the sine-fit residual, retrieval of transmission, differential phase and dark field from sample and reference stacks, dark field to scattering width, and integration of the differential phase into phase and thickness (refusing wrapped input). The four visibility maxima of the carpet sit on the fractional Talbot distances (m − 1/2)p²/(4λ) to within the 0.89 mm grid, at height 1.2732 (4/π for a square wave), and agree with an FFT propagation of the sampled field to 8e-4. A 2 mm focal spot washes the fringes out to x0.001; a source grating at the Lau period (41 slits) restores 2/π = 0.637, and detuning it by 1/41 kills them again (matching an incoherent point sum to 6e-5). In a short-fibre composite plate the 2 µm crack reaches CNR 13.5 in the dark field against 1.34 in absorption, the cos 2ω of four grating directions recovers fibre orientation to a median 2.7° (90th percentile 6.6°), and the weld line behind the hole reads 89.8° for 90°. Probe: with only 0° and 90° the diagonal fibres are off by 30.3°. Integrating the differential phase recovers a rod’s thickness to 1.0 %, and the op refuses once the true |
φ | exceeds π. Honest limit: a slab whose sharp edge pushes a single pixel past π wraps invisibly and the op silently returns -857 µm for 2000 µm. Assuming P = N for a scan of 20.85 steps per period leaves 0.063 rad of phase error, the estimated P 0.022 rad; dividing the residual by the fitted mean biases the minimum by +0.03 steps (the op divides by the raw mean). Real data (CC BY 4.0, Zenodo 16944391): 20.80 and 22.30 steps per period for the 22-frame reference and 24-frame object scans (residual 0.0073 to 0.0044); the strongly absorbing sphere scatters little (-ln D 0.17) while the fibre bundle scatters strongly (0.55), and the differential phase changes sign across the sphere. Nothing learned, nothing calibrated, 22 gates (with the real-data chapter run; 19 when the data is absent, as in CI).* |
Terms: Talbot effect — A near-field diffraction effect in which coherent illumination of a periodic structure reproduces its image, or fractional images, at specific free-space distances without a lens.; Lau — An interferometric arrangement that divides a spatially incoherent extended source with a source grating and uses a period condition to recover visibility at another grating.; Talbot carpet — A two-dimensional plot of intensity behind a periodic grating versus transverse position and propagation distance, showing self-images and fractional self-images as a woven pattern.
↑ The measurement ―― 位相 π の格子(28 keV、p1 = 4.36 µm)の後ろの強度 I(x, z)。横 = 第 1 格子からの距離 0〜z_T/2(429 mm)、縦 = 2 周期。周期 p1/2 の縞が結ぶ距離が分数タルボ距離 (m − 1/2)p1²/(4λ) = 54, 161, 268, 376 mm。z = 0 は純粋な位相格子なので強度は一様(縞が無い)。 (figure labels are in Japanese; the numbers are the same)
↑ 点線 = 閉形式の距離。
↑ 開口 1/2 の線源格子。
↑ 中央: 色相 = 向き、明るさ = 異方性の強さ。
↑ 正直な限界: 鋭い縁の 1 画素だけが π を超える板は、折り返した値が背景と π 以内に収まるので見えずに通る(中央の厚さ -857 µm、真値 2000 µm)。
↑ The animation ―― 格子の向きを 0〜180° に回したときの暗視野(4 方位から戻した a、b、θ で描く)。繊維が格子の感度の向きに直交すると暗くなり、明暗の帯が向きの場に沿って流れる。
py -3.11 examples/poc_grating_interferometry.py
Source: examples/poc_grating_interferometry.py
This run produced 12 figures in total - see them all
Ops used (notes): angular_spectrum_propagate · dpc_integrate · grating_step_period_estimate · grating_stepping_simulate · grating_three_image_retrieve · talbot_carpet · talbot_grating_design · talbot_source_visibility
In a food X-ray image, thick meat and a thin hard fragment can look the same. Counting photons in 2 energy windows and separating thickness from material removed false fragments made by overlapping meat. The thinnest aluminium that can still be told apart matched the closed-form prediction.
↑ Bone and Glass in Meat, Separated by Material with Two X-ray Energies (Dual-Energy and Photon-Counting X-ray) ―― A single X-ray transmission image of food measures one number per pixel, L = -ln(I/I0), so a thick piece of meat and a thin hard foreign body cannot be told apart. A detector that counts photons in two energy windows can: the shape of mu(E) differs by material, so thickness and material separate in every pixel (the Alvarez and Macovski 1976 basis decomposition). Fourteen new ops (spectral_xray in opstomography): the X-ray columns of the material table (material_xray_attenuation and three more, a subset of NIST SRD 126 plus a mixture rule) and ten dualxray ops (tube spectrum, beam quality, detector channels, projection, basis decomposition, CRLB, R value, effective Z, discrimination error rate). mu/rho matches the NIST published values (Cu at 8 keV 52.55; across the K edge 38.4 to 278.4), and the tube model’s half-value layers agree with the eight IEC 61267 qualities to within 5.8 % (the model was shaped on a public spectrum calculator (MIT); the IEC values were not used to fit it). On a meat tray 25 to 60 mm thick the bone-and-glass step is 0.9 times the meat unevenness, one threshold flags 86 % of the meat, and even the high band shows two false lumps at the edge of a meat overlap, whereas the Al image from a lean-meat plus Al decomposition has 0 false lumps. A confounded pair of thick PE and thin Al gives d’ = 0 in one window; the two-window error rate Phi(-d’/2) agrees with Monte Carlo to 0.004, and the Al thickness below which they cannot be separated (5 % error) is 0.125 mm in closed form and 0.126 mm measured, 0.364 mm under 40 mm of meat. The decomposition noise reaches 0.995 and 0.997 times the Cramer-Rao bound. How it breaks: decomposing without knowing 25 % charge sharing turns meat into Al, and a monochromatic approximation biases Al by -1.5 mm under 6 cm of meat; the apparent Z of a buried foreign body also moves with density (PVC at 13.9 reads as 18.7). Learning 0, calibration 0, 21 gates.
↑ The measurement ―― W 陽極の管(Kramers の厚い陽極 + 自己吸収 + K 線 59 / 67 keV)を Al 2.5 mm で濾した光子スペクトル(半価層 2.74 mm Al、平均 42.6 keV)。CdTe 2 mm の吸収効率をかけ、堆積エネルギーで 2 つの窓に分ける。 (figure labels are in Japanese; the numbers are the same)
↑ 低いエネルギーでは光電効果(≈ Z³/E³)で Z の大きい材料ほど急に増え、高いエネルギーではコンプトン散乱で電子密度に揃う。
↑ 1 枚の透過像(灰色)の上に、Al の像で浮いた画素だけ、基底の向きから読んだ見かけの Z を色で重ねた(緑 ≈ 12〜14、黄〜橙 ≈ 15〜20、赤 ≈ 25 以上)。
↑ 高い窓だけでは同じ L(1 枚の像の d′ = 0)。
↑ 肉 4 cm の下の Al の厚さの雑音(下限)。
py -3.11 examples/poc_dual_energy_xray.py
Source: examples/poc_dual_energy_xray.py
This run produced 12 figures in total - see them all
Ops used (notes): basis_decomposition_crlb · basis_material_decompose · dual_energy_separability · dual_kvp_channels · ellipse · material_effective_z · material_from_sellmeier · material_xray_attenuation · material_xray_constants · photon_counting_channels · reflect · xray_beam_quality · xray_channel_projection · xray_tube_spectrum
A public triplet prescription receives explicit rows for curvature, thickness, refractive index, Abbe number, decentre, tilt, astigmatic surface irregularity, wedge, and group decentre. Eight new operations combine distributions, central-difference sensitivity, Monte Carlo, a Wilson 95% yield interval, multiple compensators, and assembly travel. The weak-wedge image shift agrees with f(n-1)alpha to 6e-8; the real-ray OPD RMS of one fringe of astigmatic irregularity agrees with the closed form to 1.3%. In the 16-trial CI run yield is 0.688 and becomes 1.000 with inner-gap and image-plane compensation. Uniform, normal, and truncated-normal distributions at the same plus/minus tolerance yield 0.750 / 1.000 / 1.000; tightening the leading row while loosening low-sensitivity rows gives 0.833. Learning 0, calibration 0.
↑ Budgeting Lens Manufacturing Tolerances through MTF (modulation transfer function), Yield, and Compensator Travel ―― A public triplet prescription receives explicit rows for curvature, thickness, refractive index, Abbe number, decentre, tilt, astigmatic surface irregularity, wedge, and group decentre. Eight new operations combine distributions, central-difference sensitivity, Monte Carlo, a Wilson 95% yield interval, multiple compensators, and assembly travel. The weak-wedge image shift agrees with f(n-1)alpha to 6e-8; the real-ray OPD RMS of one fringe of astigmatic irregularity agrees with the closed form to 1.3%. In the 16-trial CI run yield is 0.688 and becomes 1.000 with inner-gap and image-plane compensation. Uniform, normal, and truncated-normal distributions at the same plus/minus tolerance yield 0.750 / 1.000 / 1.000; tightening the leading row while loosening low-sensitivity rows gives 0.833. Learning 0, calibration 0.
Terms: MTF — The magnitude of the optical transfer function, showing how image contrast is transferred at each spatial frequency.; Wilson 95 % — A binomial-proportion confidence interval obtained by inverting the normal score test, generally more reliable than the Wald interval for small samples.
↑ The measurement ―― 同じ試行から得た MTF と RMS spot。赤線は合否限界。 (figure labels are in Japanese; the numbers are the same)
↑ 像面だけの場合と内部間隔を加えた場合を比較。
↑ 一様・正規・±公差で切った正規分布の歩留まり。
↑ 線形予測と Monte-Carlo 実測を明示。
↑ 同じ処方の基準像と、1 arcmin のくさびを入れた像。
↑ The animation ―― 感度首行を -tol から +tol へ掃引した点像。
py -3.11 examples/poc_lens_tolerancing.py
Source: examples/poc_lens_tolerancing.py
This run produced 7 figures in total - see them all
Ops used (notes): chief_ray · lens_system · opd_samples · ray_bundle · tolerance_compensators · tolerance_criteria · tolerance_monte_carlo · tolerance_report · tolerance_sensitivity · tolerance_table · tolerance_table_from_zmx · tolerance_table_zmx
A main lens, square periodic MLA and sensor are reverse-traced to a raw frame, with a flat field captured through the same prescription. The flat field calibrates the MLA, the raw frame is decoded, and only that decoded light field feeds the EPI (epipolar-plane image), refocus and continuous metric-depth outputs. A causal gate changes raw data and requires all three outputs to change. The former three-class depth oracle is removed; the prescription slope-to-distance relation round-trips unseen 0.75 m and 1.3 m depths below 1e-9 m error. The camera section and F-number ratio panels are explicitly conceptual diagrams, not validation evidence.
↑ A Prescription-Based Plenoptic Camera ―― A main lens, square periodic MLA and sensor are reverse-traced to a raw frame, with a flat field captured through the same prescription. The flat field calibrates the MLA, the raw frame is decoded, and only that decoded light field feeds the EPI (epipolar-plane image), refocus and continuous metric-depth outputs. A causal gate changes raw data and requires all three outputs to change. The former three-class depth oracle is removed; the prescription slope-to-distance relation round-trips unseen 0.75 m and 1.3 m depths below 1e-9 m error. The camera section and F-number ratio panels are explicitly conceptual diagrams, not validation evidence.
Terms: EPI — A slice of a light field over one spatial and one angular coordinate, where a 3-D point traces a line whose slope depends on depth.
↑ The measurement ―― 480 x 480 sensor, 32 x 32 MLA, 15 pixels per microlens; five prescription-traced rays. (figure labels are in Japanese; the numbers are the same)
↑ F-number ratios 0.75, 1.00 and 1.50; matched diameter/pitch = 1.000.
↑ 15 x 15 decoded views; EPI disparity is 0.6 px per view step.
↑ Truth and estimate at 600, 1000 and 1800 mm; signed error and colour bar.
↑ Same MLA: plenoptic 1.0 images the pupil; focused 2.0 images scene fragments.
↑ The animation ―― 24-frame 480-pixel refocus sweep; all frames pass the content gates.
py -3.11 examples/poc_plenoptic_camera.py
Source: examples/poc_plenoptic_camera.py
This run produced 9 figures in total - see them all
Ops used (notes): lens_scene · lens_system · lf_depth_from_focus · lf_refocus · mla_surface · paraxial_trace · plenoptic_capture · plenoptic_decode · plenoptic_mla_calibrate · plenoptic_refocus_depth · plenoptic_system
One ledger of fourteen spherical and faceted particles drives both images. Area-equivalent diameter, maximum slope, watershed splitting of touching pairs, stereo height and limited-angle reconstruction are scored against the shared truth. For a 52 nm particle the scanning-image threshold is +4.16 nm, the transmission image is -0.18 nm at focus and +9.82 nm at 12000 nm defocus. The scanning bias follows the closed-form Gaussian rim width 2 sqrt(2 ln 2) sigma, while the dark transmission fringe follows sqrt(lambda defocus) within ten per cent. Material contrast is checked against published backscatter coefficients; 3-D measurements are checked against parallel-projection geometry and a full-angle reconstruction. No new operators, no learning and no calibration.
↑ Measuring the Same Particles in Scanning and Transmission Images — Which Is Right, 52 nm or 48 nm? ―― One ledger of fourteen spherical and faceted particles drives both images. Area-equivalent diameter, maximum slope, watershed splitting of touching pairs, stereo height and limited-angle reconstruction are scored against the shared truth. For a 52 nm particle the scanning-image threshold is +4.16 nm, the transmission image is -0.18 nm at focus and +9.82 nm at 12000 nm defocus. The scanning bias follows the closed-form Gaussian rim width 2 sqrt(2 ln 2) sigma, while the dark transmission fringe follows sqrt(lambda defocus) within ten per cent. Material contrast is checked against published backscatter coefficients; 3-D measurements are checked against parallel-projection geometry and a full-angle reconstruction. No new operators, no learning and no calibration.
↑ The measurement ―― Truth height only: turbo-like pseudo-colour with a nanometre colour bar in the margin. (figure labels are in Japanese; the numbers are the same)
| [](https://raw.githubusercontent.com/furuse-kazufumi/fullseye/master/docs/articles/assets/poc/poc_sem_tem_same_particle/03_tem_raw_overlay_error.png) |
| *↑ Bright-field in-focus and under-focus output | derived diameters | absolute residual.* |
↑ この回の図
↑ この回の図
↑ この回の図
↑ The animation ―― 24 greyscale bright-field frames with data bar; first and last defocus differ
py -3.11 examples/poc_sem_tem_same_particle.py
Source: examples/poc_sem_tem_same_particle.py
This run produced 8 figures in total - see them all
Ops used (notes): color_bar · filtered_backprojection · radon_transform · reflect · scale_bar · sem_electron_mc · sem_image · sem_render · tem_image_weak_phase · tem_multislice · tem_projected_potential · tem_wavelength · text_box · watersheds_marker
The same 320 by 240 scene is shown as IR amplitude, colour-coded distance over 1.5–6.0 m, and a point cloud. The amplitude-distribution W1 distance between synthesis and measurement is 0.145 (gate 0.594). The remaining figures add physical effects one at a time; compare amplitude with range noise (log slope -0.878 in measurements and -1.027 in synthesis); reproduce wall-junction bowing (-551 mm measured and +75 mm synthetic, with matching signs after aligning profile direction); separate transparent-pane returns 1.35 m apart; expose mixed pixels around a thin pole; show direct time-of-flight histograms and decoded depth; and tabulate three box-volume errors (+0.16, +0.47 and +0.29 percent). The final animation is a 48-frame, 576 by 432, 8 fps light front moving from 1.4 to 6.2 m. Real frames are not redistributed: only curves and aggregate measurements are shown. No new ops, no learning, no calibration, 13 gates.
↑ Indirect and Direct Time of Flight on One 3-D Scene — Multipath, Mixed Pixels, Pile-up and Volume Error ―― The same 320 by 240 scene is shown as IR amplitude, colour-coded distance over 1.5–6.0 m, and a point cloud. The amplitude-distribution W1 distance between synthesis and measurement is 0.145 (gate 0.594). The remaining figures add physical effects one at a time; compare amplitude with range noise (log slope -0.878 in measurements and -1.027 in synthesis); reproduce wall-junction bowing (-551 mm measured and +75 mm synthetic, with matching signs after aligning profile direction); separate transparent-pane returns 1.35 m apart; expose mixed pixels around a thin pole; show direct time-of-flight histograms and decoded depth; and tabulate three box-volume errors (+0.16, +0.47 and +0.29 percent). The final animation is a 48-frame, 576 by 432, 8 fps light front moving from 1.4 to 6.2 m. Real frames are not redistributed: only curves and aggregate measurements are shown. No new ops, no learning, no calibration, 13 gates.
↑ The measurement ―― 全パネル同じ距離範囲。暗部の荒れと隅の湾曲が順に現れる。 (figure labels are in Japanese; the numbers are the same)
↑ 対数傾きは実測 -0.878、合成 -1.027。
↑ 実測は曲線だけを掲載し、フレームを再配布しない。
↑ 手描きマスクではなく3×3サブ光線のフェーザ和。
↑ 強い画素では最初の光子が早い側へ偏る。
↑ The animation ―― 48枚、576×432。各時刻を99パーセンタイル・γ=0.5で階調化。
py -3.11 examples/poc_tof_camera.py
Source: examples/poc_tof_camera.py
This run produced 9 figures in total - see them all
Ops used (notes): bilateral_filter_depth · depth_to_points · dtof_cube_depth · dtof_cube_simulate · points_render · threshold
A synthetic 3 m-radius tunnel is captured by two fisheyes and stitched back to a panorama: measured seam error 0.01569 and 0.000 percent blind pixels. The same scene checks solid-angle weighting, moving viewports, 16 of 32 requested tiles, spherical stereo that recovers 3 m as 3.000000 m, rotation correction that raises WS-PSNR from 20.13 to 33.51 dB, cylindrical defect unwrapping, and an actual red/cyan pair. Ideal filters have zero leakage; 10 percent leakage measures 0.0693/0.0014 for the two eyes. On-screen parallax is 1.80 mm. Eight chapters, eight figures, and a 30-frame video. No learning and no calibration.
↑ Measurable Omnidirectional Inspection, Stereo Display and Moving Viewports ―― A synthetic 3 m-radius tunnel is captured by two fisheyes and stitched back to a panorama: measured seam error 0.01569 and 0.000 percent blind pixels. The same scene checks solid-angle weighting, moving viewports, 16 of 32 requested tiles, spherical stereo that recovers 3 m as 3.000000 m, rotation correction that raises WS-PSNR from 20.13 to 33.51 dB, cylindrical defect unwrapping, and an actual red/cyan pair. Ideal filters have zero leakage; 10 percent leakage measures 0.0693/0.0014 for the two eyes. On-screen parallax is 1.80 mm. Eight chapters, eight figures, and a 30-frame video. No learning and no calibration.
↑ The measurement ―― Seed 275; tunnel radius 3.00 m; crack angular width 3.00 deg. (figure labels are in Japanese; the numbers are the same)
↑ Measured seam MAE 0.0157; blind pixels 0.000 % (reported, never filled silently).
↑ Horizontal half-height width ratio 0.314; defect solid angle 0.03687 sr versus unweighted 0.02399 sr.
↑ At 3.00 m: disparity 3.477 px and inverted depth 3.000000 m.
↑ WS-PSNR improved from 20.13 to 33.51 dB; residual 0.0078.
↑ The animation ―― Thirty differing frames; the first frame is already moving.
py -3.11 examples/poc_omni360_inspection.py
Source: examples/poc_omni360_inspection.py
This run produced 9 figures in total - see them all
Ops used (notes): anaglyph_crosstalk · equirect_defect_unwrap_pipe · equirect_filter_gaussian · equirect_from_fisheye_pair · equirect_rotate · equirect_stabilize · equirect_weight_map · ods_depth_to_disparity · ods_disparity_to_depth · omni_anaglyph_compose · stereo_parallax_budget · video360_tile_coverage · video360_viewport_track · viewport_from_equirect · ws_psnr