Spaces:
Running
Running
| <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Compression force</title><style> | |
| :root{--bg:#0b1020;--fg:#e8eefb;--dim:#8ea0c2;--line:#1e2a45;--card:#111a2e; | |
| --accent:#ffc46b;--ok:#7be0a0;--bad:#ff8f7a; | |
| --s0:12px;--s1:14px;--s2:16px;--s3:20px;--s4:28px;--s5:40px} | |
| *{box-sizing:border-box} | |
| body{margin:0;background:var(--bg);color:var(--fg);font-size:var(--s2); | |
| font-family:'IBM Plex Sans',system-ui,sans-serif;line-height:1.6} | |
| .wrap{max-width:1100px;margin:0 auto;padding:0 20px 72px} | |
| a{color:var(--accent)} | |
| h1{font-size:var(--s5);line-height:1.15;margin:40px 0 8px;font-weight:650} | |
| h2{font-size:var(--s4);margin:44px 0 10px;font-weight:600} | |
| h3{font-size:var(--s3);margin:28px 0 6px;font-weight:600} | |
| p{margin:10px 0;max-width:70ch} | |
| .dim{color:var(--dim);font-size:var(--s1)} | |
| .bad{color:var(--bad);font-size:var(--s0)} | |
| nav{display:flex;gap:8px;flex-wrap:wrap;margin:18px 0 4px} | |
| nav a{display:inline-block;padding:10px 16px;min-height:44px;line-height:24px; | |
| border:1px solid var(--line);border-radius:999px;text-decoration:none; | |
| background:var(--card)} | |
| nav a[aria-current]{border-color:var(--accent)} | |
| /* built by other scripts and published beside this site */ | |
| nav a.ext{border-style:dashed} | |
| .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); | |
| gap:12px;margin:20px 0} | |
| .card{background:var(--card);border:1px solid var(--line);border-radius:12px; | |
| padding:16px} | |
| .card b{display:block;font-size:var(--s4);color:var(--accent);font-weight:650} | |
| .card span{color:var(--dim);font-size:var(--s1)} | |
| figure{margin:24px 0} | |
| figure img{width:100%;border-radius:10px;border:1px solid var(--line); | |
| background:#fff} | |
| figcaption{color:var(--dim);font-size:var(--s1);margin-top:8px;max-width:80ch} | |
| table{border-collapse:collapse;width:100%;margin:14px 0;font-size:var(--s1)} | |
| th,td{border-bottom:1px solid var(--line);padding:9px 10px;text-align:right} | |
| th:first-child,td:first-child{text-align:left} | |
| th{color:var(--dim);font-weight:500} | |
| td b{color:var(--ok)} | |
| details{background:var(--card);border:1px solid var(--line);border-radius:10px; | |
| padding:12px 16px;margin:16px 0} | |
| summary{cursor:pointer;color:var(--dim);font-size:var(--s1);min-height:44px; | |
| display:flex;align-items:center} | |
| code{background:#0d1526;padding:2px 6px;border-radius:5px;font-size:var(--s1)} | |
| /* Inline links in prose measured 47x16 and 54x16 at 375 px — a tap target, | |
| because a finger does not know it is "only prose". Padding alone would | |
| break the line box, so the height comes from an inline-block with the | |
| line-height carrying it. */ | |
| p a,figcaption a{display:inline-block;min-height:44px;line-height:44px; | |
| padding:0 2px} | |
| /* The <pre> pipeline diagram and the results table are the two things wider | |
| than a phone. Let each scroll inside its own box rather than pushing the | |
| document sideways — a horizontally scrolling PAGE hides content with no | |
| affordance, a scrolling code block is a known idiom. */ | |
| pre{background:var(--card);border:1px solid var(--line);border-radius:10px; | |
| padding:14px 16px;overflow-x:auto;font-size:var(--s1);max-width:100%} | |
| .tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch} | |
| .tablewrap table{min-width:640px} | |
| img{max-width:100%;height:auto} | |
| /* Subscripts default to a fraction of the parent and rendered at 11.7px — | |
| off the --s0..--s5 scale the audit counts. Pinned to the smallest step. */ | |
| sub,sup{font-size:var(--s0);line-height:0} | |
| </style></head><body><div class="wrap"><nav><a href="index.html">overview</a><a href="method.html">method</a><a href="results.html">results</a><a href="sensors.html">sensors</a><a href="force.html" aria-current="page">force</a><a href="gallery.html">gallery</a><a href="workbench.html">3D workbench</a><a href="probes/index.html" class="ext">probe clips</a><a href="testset/index.html" class="ext">test set</a><a href="sim/index.html" class="ext">simulator</a></nav> | |
| <h1>The compression force, and which way it pushes</h1> | |
| <p><code>force_{side}_normal_n</code> is a <b>magnitude</b> in newtons, recovered | |
| from the tactile image. It becomes a displacement through one constant: | |
| <b>penetration = F / k</b> with <b>k = 2 N/mm</b> | |
| (<code>dexforce.STIFFNESS_N_PER_M</code> — import it, do not retype it), and a | |
| direction: the gel normal <b>in the sensor's own body frame</b>.</p> | |
| <pre><code>from force_recovery.dexforce import gel_axis, STIFFNESS_N_PER_M | |
| n_hat = R_from_quat(pose[3:7]) @ gel_axis(task, side) # world unit vector | |
| target = pose[:3] + (force_n / STIFFNESS_N_PER_M) * n_hat</code></pre> | |
| <p><b>World vertical is not that direction</b>, under either candidate axis — | |
| quoting only the active one would argue the point with the very axis in dispute. | |
| Over contact frames the gel normal sits a median | |
| 7.7°–25.1° from world −z | |
| on motherboard and <b>23.3°–43.2°</b> | |
| on pushT, where 70–98% | |
| of 8,369 frames exceed 15°. Pushing "straight down" is wrong by at | |
| least the smaller of each pair.</p> | |
| <h2>Two candidate axes, and why the choice is not settled</h2> | |
| <p>The default is sensor-local <b>−y</b>: the Mini's sensing face is normal to the | |
| body's y. The calibration files also carry <code>gel_axis_in_rigid</code> | |
| (<code>source="dual_ball"</code>), which is | |
| normalize(gelball − refball) — the line between two calibration ball | |
| centres, from <b>3</b> poses. <b>It never measured the gel | |
| surface</b>; it is the normal only if the fixture held both balls along it. The | |
| two disagree by 21.2° (left) and | |
| 22.4° (right).</p> | |
| <div class="tablewrap"><table><thead><tr> | |
| <th>test</th><th>angle from board normal<br><span class="dim">dual_ball / <b>−y</b></span></th> | |
| <th>corr(dF, v·n̂)<br><span class="dim">dual_ball / <b>−y</b></span></th> | |
| <th>image tilt R²<br><span class="dim">dual_ball / <b>−y</b></span></th> | |
| <th>sv₁/sv₂</th></tr></thead><tbody> | |
| <tr><td>left</td><td>7.1° / <b>25.6°</b></td><td>+0.085 / <b>+0.053</b> <span class="dim">(-y better on 3% of 31 eps)</span></td><td>-0.151 / <b>+0.009</b></td><td>1.09</td></tr> | |
| <tr><td>right</td><td>18.1° / <b>7.7°</b></td><td>+0.116 / <b>+0.116</b> <span class="dim">(-y better on 61% of 31 eps)</span></td><td>-0.002 / <b>+0.006</b></td><td>1.04</td></tr> | |
| </tbody></table></div> | |
| <p class="dim">Board normal: pressing >6 N on a level board | |
| (18,054 and 20,166 frames), the gel normal | |
| should point near world −z. corr(dF, v·n̂): pressing in raises force — uses no | |
| world frame and no table. Image tilt: R² of a single linear map from the | |
| pose-predicted tilt to the gel's own deformation gradient.</p> | |
| <h2>What the three tests actually say</h2> | |
| <p><b>They disagree, and the right sensor is the suspect.</b> The left sensor's | |
| dual-ball axis wins both proxies; the right sensor's loses one and ties the other. | |
| The right calibration also reports <code>depth_offset_mm = | |
| 0</code> where the left reports | |
| -5 — its ball centre was never backed off by a ball | |
| radius to reach the gel surface.</p> | |
| <p><b>And this dataset cannot settle it.</b> Two independent reasons. Kinematically | |
| the problem is ill-conditioned: sv₁/sv₂ is 1.09 and | |
| 1.04, so changing the axis by 60° barely moves any | |
| concentration score. And the gel's own deformation — a <b>real</b> signal, coherent to | |
| 10.6° between samples 7 contact rows apart — carries the geometry of whatever was touched | |
| (components, edges, connectors), not the sensor's tilt: R² ≈ 0 for both candidates. | |
| The contacted surface is not a known plane, so the most direct observable is | |
| confounded at the source.</p> | |
| <p><b>What would settle it</b> is a measurement, not more analysis: press the sensor | |
| flat on a known flat plate at ten-plus widely varied orientations. Kinematics | |
| becomes well-conditioned, and the image gives a null test — on a true plane the | |
| deformation gradient vanishes when the press is normal. That same run also answers | |
| whether the OptiTrack rigid body was redefined between calibration epochs, which | |
| the copied gel calibration cannot.</p> | |
| <p class="dim">Cost of the current default: switching from dual_ball to −y moves | |
| <code>force_*_target_pose</code> by a median 0.57 mm | |
| (max 1.53) on motherboard and 0.30 mm | |
| (max 1.45) on pushT, because F/k is itself only a few mm. | |
| Force magnitudes and penetration are unaffected — they are scalars.</p> | |
| </div></body></html> |