force_{side}_normal_n is a magnitude in newtons, recovered
from the tactile image. It becomes a displacement through one constant:
penetration = F / k with k = 2 N/mm
(dexforce.STIFFNESS_N_PER_M — import it, do not retype it), and a
direction: the gel normal in the sensor's own body frame.
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
World vertical is not that direction, 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 23.3°–43.2° on pushT, where 70–98% of 8,369 frames exceed 15°. Pushing "straight down" is wrong by at least the smaller of each pair.
The default is sensor-local −y: the Mini's sensing face is normal to the
body's y. The calibration files also carry gel_axis_in_rigid
(source="dual_ball"), which is
normalize(gelball − refball) — the line between two calibration ball
centres, from 3 poses. It never measured the gel
surface; it is the normal only if the fixture held both balls along it. The
two disagree by 21.2° (left) and
22.4° (right).
| test | angle from board normal dual_ball / −y |
corr(dF, v·n̂) dual_ball / −y |
image tilt R² dual_ball / −y |
sv₁/sv₂ |
|---|---|---|---|---|
| left | 7.1° / 25.6° | +0.085 / +0.053 (-y better on 3% of 31 eps) | -0.151 / +0.009 | 1.09 |
| right | 18.1° / 7.7° | +0.116 / +0.116 (-y better on 61% of 31 eps) | -0.002 / +0.006 | 1.04 |
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.
They disagree, and the right sensor is the suspect. 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 depth_offset_mm =
0 where the left reports
-5 — its ball centre was never backed off by a ball
radius to reach the gel surface.
And this dataset cannot settle it. 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 real 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.
What would settle it 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.
Cost of the current default: switching from dual_ball to −y moves
force_*_target_pose 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.