Recovering Force-Related Actions for the React Tactile Dataset

React records GelSight-Mini images and OptiTrack sensor poses, but — like all UMI-style datasets — no applied force: demonstrated pose equals achieved pose, so the F = k(target − actual) channel of a stiffness controller is identically zero, and contact-rich pressure cannot be reproduced from the actions alone. Two methods, implemented and evaluated on the released data, put it back.

GelSight Mini (markerless gel) no F/T sensorno force ground truth dataset: yxma/React → action processing deep-dive

What was tried first — and why it failed

FEATS transfer: a negative result

The plan matched FARM (arXiv:2510.13324): run FEATS (U-net, GelSight-Mini images → FEA-supervised force distributions, MAE < 1 N) offline over the recordings. It does not transfer: FEATS is trained on marker-dot gel, and the React sensors use markerless gel — different texture, different illumination profile.

training sensor vs our sensor

Left: FEATS training sensor (63 marker dots). Right: React sensor, markerless. On this input the network returns its no-contact output for every frame — the strongest-contact frame of pushT/episode_000 predicts −0.040 N total, identical to a free frame (−0.042 N). Dot-based per-sensor calibration is impossible for the same reason.

Method 1 — Photometric-stereo depth → Winkler normal force

GelSight frame
640×480 RGB
per-pixel MLP
(gsrobotics, markerless)
surface normals Poisson (DCT)
integration
background plane
removal (robust fit)
indentation δ(x,y) [mm] Winkler foundation
F = c · Σδ · dA

The gel pad is modelled as a bed of independent springs (thin-elastic-layer approximation); the constant c is fitted on FEATS ground truth rather than assumed (see the external-validation section). Per-sensor calibration uses the 15 lowest-intensity frames of each episode: median depth becomes the zero map and the MAD of the plane-removed residuals sets a 5σ contact threshold — a fixed threshold cannot work because reconstruction noise differs per sensor (measured ~10 µm after plane removal). Each frame additionally gets a robust background-plane fit, which removes the frame-to-frame illumination tilt that otherwise dominates integrated depth.

raw | diff | depth

Strongest contact of motherboard/episode_000 (left sensor): raw frame | difference vs reference | reconstructed depth. Indentation localises exactly where the image difference shows contact.

depth panels pushT depth panels motherboard

Evaluation (no ground truth → falsifiable properties instead)

E1.1 specificity — no-contact rows must read ≈0 N; SNR = median force on high-intensity rows / p95 on reference rows. E1.2 correlation — Spearman ρ against contact intensity, an independent statistic of the same images. E1.3 spikes — single-frame excursions are solver noise; a median-3 over fresh frames must remove them. E1.4 calibration stability across episodes.

episodesidefresh rowsmax F [N]SNRρ vs intensityspike raw→filt [%]threshold [µm]
2026-05-10/episode_000left20107.562675186.20.921.4 → 0.010
2026-05-10/episode_000right20077.279841.90.831.0 → 0.010
2026-05-10/episode_001left21579.39646.70.841.4 → 0.010
2026-05-10/episode_001right21558.301373956.40.761.1 → 0.010
2026-05-10/episode_002left30379.66623.90.781.0 → 0.010
2026-05-10/episode_002right302515.481977041.60.801.6 → 0.010
2026-05-10/episode_003left26295.00941.30.820.1 → 0.010
2026-05-10/episode_003right26293.102.00.810.5 → 0.010
2026-05-10/episode_004left6903.280.70.700.1 → 0.010
2026-05-10/episode_004right6924.772.00.890.3 → 0.010
2026-05-10/episode_005left2202.871.50.820.0 → 0.010
2026-05-10/episode_005right2213.5516.60.740.0 → 0.010
2026-05-10/episode_006left11533.1172398.30.890.1 → 0.010
2026-05-10/episode_006right11524.181627990.20.940.0 → 0.010
2026-05-10/episode_007left5562.100.80.680.2 → 0.010
2026-05-10/episode_007right5563.211371906.60.900.2 → 0.010
2026-05-10/episode_008left3762.71309.30.930.0 → 0.010
2026-05-10/episode_008right3772.093.30.820.3 → 0.010
2026-05-10/episode_009left5234.53119.10.880.6 → 0.010
2026-05-10/episode_009right5265.42166207.30.921.1 → 0.010
2026-05-10/episode_010left3004.125.20.910.7 → 0.010
2026-05-10/episode_010right2992.383.30.732.0 → 0.010
2026-05-10/episode_011left6358.8970.40.880.0 → 0.010
2026-05-10/episode_011right6362.162668.30.880.0 → 0.010
2026-05-11/episode_003left27969.4942.50.781.7 → 0.010
2026-05-11/episode_003right280417.322880060.20.782.4 → 0.010
2026-05-11/episode_004left73510.49591.40.761.5 → 0.010
2026-05-11/episode_004right73610.422597437.60.781.0 → 0.010
2026-05-11/episode_005left40389.208449.80.841.0 → 0.010
2026-05-11/episode_005right403611.163092505.90.821.1 → 0.110
2026-05-11/episode_006left336110.7817.80.850.8 → 0.010
2026-05-11/episode_006right336414.074450209.10.831.1 → 0.010
2026-05-11/episode_007left19238.642166.50.820.4 → 0.010
2026-05-11/episode_007right19238.671010.90.771.1 → 0.110
2026-05-11/episode_008left382712.9712594.20.780.9 → 0.010
2026-05-11/episode_008right382413.404091582.30.851.4 → 0.010
2026-05-11/episode_009left12138.05723.40.840.7 → 0.010
2026-05-11/episode_009right121214.284749535.10.911.3 → 0.010
2026-05-11/episode_010left19034.21691.10.710.4 → 0.010
2026-05-11/episode_010right189617.674123979.60.871.7 → 0.110
2026-05-11/episode_011left21337.9415.50.820.8 → 0.010
2026-05-11/episode_011right212710.833515466.90.872.0 → 0.110
2026-05-11/episode_012left413110.33514.80.891.4 → 0.010
2026-05-11/episode_012right413012.994057872.80.881.8 → 0.010
2026-05-11/episode_013left34991.872.30.720.2 → 0.010
2026-05-11/episode_013right35082.51716545.80.830.8 → 0.110
2026-05-11/episode_014left6751.45664.60.880.3 → 0.010
2026-05-11/episode_014right6792.53370.00.860.4 → 0.010
2026-05-11/episode_015left16501.739284.30.870.3 → 0.010
2026-05-11/episode_015right16474.701556752.20.882.9 → 0.010
2026-05-11/episode_016left32692.0457.10.840.8 → 0.010
2026-05-11/episode_016right32524.401357047.60.822.0 → 0.010
2026-05-11/episode_017left398020.571407.60.831.3 → 0.010
2026-05-11/episode_017right395422.8065.70.891.5 → 0.012
2026-05-19/episode_000left5188.699.30.932.7 → 0.210
2026-05-19/episode_000right50611.126884184.80.944.0 → 0.210
2026-05-19/episode_001left2518.231.60.894.0 → 0.424
2026-05-19/episode_001right2469.451.50.932.5 → 0.010
2026-05-19/episode_002left5906.95975.90.944.6 → 0.010
2026-05-19/episode_002right5949.6822.10.925.4 → 0.210
2026-05-19/episode_003left295.041.10.183.7 → 0.030
2026-05-19/episode_003right314.984836304.20.936.9 → 0.070
2026-05-19/episode_004left354.141.10.806.1 → 0.023
2026-05-19/episode_004right416.081.70.952.6 → 0.010
2026-06-18/episode_000left6891.97164912.10.770.6 → 0.110
2026-06-18/episode_000right6933.7019362.20.871.4 → 0.010
2026-06-18/episode_001left40997.23317003.90.800.5 → 0.010
2026-06-18/episode_001right41536.65575866.70.790.7 → 0.010
2026-06-18/episode_002left39564.52337766.30.800.4 → 0.010
2026-06-18/episode_002right39945.59793320.50.841.2 → 0.010
2026-06-18/episode_003left40213.52209622.60.770.1 → 0.010
2026-06-18/episode_003right40654.98628000.00.830.7 → 0.010
force timeline pushT force timeline motherboard

12 s around each episode's force peak: live force bar under the tactile stream (median-3 filtered). Left: pushT (light, intermittent contact). Right: motherboard (sustained presses).

External validation — FEATS ground truth

React has no force ground truth, but FEATS (arXiv:2411.03315) does: a GelSight Mini pressed by a CNC machine against 24 indenters, with FEA-derived force labels. Running our estimator on its val split is a transfer test — marker-dot gel, different pad, single global no-contact reference — i.e. strictly harder than the React setting, where reference frames come from the same episode seconds away.

FEATS validation scatter
metricvaluescope
Spearman ρ (pooled)0.70 normal loading, ≤30 N, n=150
Pearson r (pooled)0.63
MAE after scale calibration4.7 N
ρ within one indenter family0.66–0.94zelda 0.94, cylinder 0.75, triangle 0.70
ρ on unseen indenter shapes0.85test_unknown_indenters split, n=100 — shape generalization holds
scale stability across splits2–4×fitted N/mm³ varies 1.9–7.2 between sessions/sensors: within-episode relative force is reliable, cross-sensor absolute scale is not
ρ on shear-loaded captures−0.15excluded & documented: shear churns the image without adding indentation volume
fitted scale1.89 N/mm³ 40× the assumed Winkler constant — the fit absorbs the depth-unit and gel-constant assumptions; React forces are reported in FEATS-calibrated newtons

What the validation bought. Chasing the initial ρ = 0.42 exposed two pipeline defects that also affected React inference: a global illumination tilt dominating the integrated depth (fixed by a robust background-plane fit per frame — React contact thresholds tightened from 50 µm to 10 µm) and marker-dot imprints from the SDK's mask-and-interpolate path (fixed by inpainting the dots before the network). A quadratic background model was also tried and rejected — it absorbed real contact (ρ 0.61→0.50). Sanity check on scale: the strongest motherboard press reads ~0.2 N under the theoretical constant but ~7 N calibrated — the latter is what pressing a connector home actually takes.

Method 2 — DexForce-style force-informed position targets

Rather than adding force as a new action dimension, the estimated normal force becomes a virtual position target past the contact surface (DexForce, arXiv:2501.10356):

ptarget = pobserved + (F̂n / k) · n̂,    k = 1500 N/m (deployment impedance)

The action stays a pose — it composes with the existing 30 Hz pose actions, needs only an impedance controller at deployment, and in free space reduces exactly to the observed pose. The pressing direction n̂ is not a guessed axis: it is the rig's dual-ball calibrated gel_axis_in_rigid (pose-to-pose consistency ≈1°), rotated into world frame per row. The naive [0,0,1] guess produced negative approach alignment at force onsets; the calibrated axis makes it positive.

virtual target offsets pushT virtual target offsets motherboard

Evaluation

E2.1 free-space invariance — zero force must leave the action exactly the observed pose. E2.2 boundedness — F/k must stay millimetre-scale to be a safe action. E2.3 roundtrip — k·‖target − pose‖ must reproduce the input force (pure algebra). E2.4 geometry — motion should align more with n̂ while pressing than in free motion.

episodesidefree-space max offset [m]penetration p50 / max [mm]roundtrip err [N]v·n̂ pressing / free
2026-05-10/episode_000left0.0e+001.10 / 5.048.2e-14+0.128 / -0.005
2026-05-10/episode_000right0.0e+000.97 / 4.856.1e-14+0.037 / +0.025
2026-05-10/episode_001left0.0e+001.33 / 6.266.4e-14+0.145 / +0.079
2026-05-10/episode_001right0.0e+001.12 / 5.546.0e-14+0.221 / +0.082
2026-05-10/episode_002left0.0e+000.95 / 6.445.2e-14+0.163 / +0.075
2026-05-10/episode_002right0.0e+001.16 / 10.327.0e-14+0.075 / +0.081
2026-05-10/episode_003left0.0e+000.61 / 3.333.6e-14-0.024 / +0.072
2026-05-10/episode_003right0.0e+000.39 / 2.066.9e-14+0.026 / +0.000
2026-05-10/episode_004left0.0e+000.71 / 2.192.5e-14-0.017 / +0.041
2026-05-10/episode_004right0.0e+000.67 / 3.184.1e-14-0.031 / -0.164
2026-05-10/episode_005left0.0e+001.01 / 1.911.8e-14-0.059 / -0.020
2026-05-10/episode_005right0.0e+000.74 / 2.372.9e-14-0.033 / +0.007
2026-05-10/episode_006left0.0e+000.69 / 2.072.6e-14-0.027 / +0.101
2026-05-10/episode_006right0.0e+000.55 / 2.793.2e-14+0.034 / -0.009
2026-05-10/episode_007left0.0e+000.55 / 1.403.9e-14-0.010 / +0.188
2026-05-10/episode_007right0.0e+000.72 / 2.142.8e-14-0.004 / -0.053
2026-05-10/episode_008left0.0e+000.37 / 1.813.6e-14-0.021 / +0.205
2026-05-10/episode_008right0.0e+000.56 / 1.392.6e-14+0.047 / -0.053
2026-05-10/episode_009left0.0e+000.85 / 3.024.1e-14-0.062 / +0.103
2026-05-10/episode_009right0.0e+001.36 / 3.614.8e-14+0.012 / +0.066
2026-05-10/episode_010left0.0e+000.72 / 2.753.0e-14-0.031 / +0.117
2026-05-10/episode_010right0.0e+000.86 / 1.592.9e-14+0.061 / +0.082
2026-05-10/episode_011left0.0e+000.47 / 5.933.5e-14-0.014 / +0.078
2026-05-10/episode_011right0.0e+000.58 / 1.444.7e-14+0.004 / +0.023
2026-05-11/episode_003left0.0e+001.34 / 6.334.5e-14+0.115 / +0.102
2026-05-11/episode_003right0.0e+001.73 / 11.556.8e-14+0.079 / +0.073
2026-05-11/episode_004left0.0e+002.38 / 6.994.5e-14+0.146 / +0.127
2026-05-11/episode_004right0.0e+001.83 / 6.956.6e-14+0.079 / +0.105
2026-05-11/episode_005left0.0e+001.70 / 6.137.3e-14+0.075 / +0.022
2026-05-11/episode_005right0.0e+001.62 / 7.448.0e-14+0.036 / -0.011
2026-05-11/episode_006left0.0e+001.95 / 7.194.2e-14+0.083 / +0.110
2026-05-11/episode_006right0.0e+002.16 / 9.388.1e-14+0.070 / +0.115
2026-05-11/episode_007left0.0e+001.56 / 5.763.3e-14+0.007 / +0.122
2026-05-11/episode_007right0.0e+001.16 / 5.787.3e-14+0.040 / +0.096
2026-05-11/episode_008left0.0e+001.95 / 8.653.6e-14+0.051 / +0.124
2026-05-11/episode_008right0.0e+002.05 / 8.936.5e-14+0.051 / +0.084
2026-05-11/episode_009left0.0e+000.82 / 5.373.2e-14+0.048 / +0.065
2026-05-11/episode_009right0.0e+001.86 / 9.524.3e-14-0.015 / +0.072
2026-05-11/episode_010left0.0e+000.74 / 2.814.6e-14+0.140 / +0.068
2026-05-11/episode_010right0.0e+001.95 / 11.785.8e-14+0.074 / +0.009
2026-05-11/episode_011left0.0e+001.03 / 5.293.4e-14+0.147 / +0.094
2026-05-11/episode_011right0.0e+001.81 / 7.224.0e-14+0.060 / +0.008
2026-05-11/episode_012left0.0e+001.02 / 6.894.2e-14+0.086 / +0.092
2026-05-11/episode_012right0.0e+001.72 / 8.666.2e-14+0.016 / +0.036
2026-05-11/episode_013left0.0e+000.25 / 1.255.0e-14+0.005 / +0.057
2026-05-11/episode_013right0.0e+000.38 / 1.677.0e-14+0.058 / +0.081
2026-05-11/episode_014left0.0e+000.22 / 0.973.7e-14-0.015 / +0.046
2026-05-11/episode_014right0.0e+000.30 / 1.696.6e-14+0.073 / +0.066
2026-05-11/episode_015left0.0e+000.27 / 1.167.2e-14-0.018 / +0.041
2026-05-11/episode_015right0.0e+000.71 / 3.143.5e-14+0.067 / +0.111
2026-05-11/episode_016left0.0e+000.30 / 1.364.0e-14+0.017 / +0.074
2026-05-11/episode_016right0.0e+000.62 / 2.935.4e-14+0.012 / +0.124
2026-05-11/episode_017left0.0e+003.94 / 13.725.0e-14+0.044 / +0.156
2026-05-11/episode_017right0.0e+004.22 / 15.207.5e-14-0.004 / +0.126
2026-05-19/episode_000left0.0e+001.51 / 5.793.0e-14+0.021 / -0.011
2026-05-19/episode_000right0.0e+003.22 / 7.413.6e-14+0.036 / -0.018
2026-05-19/episode_001left0.0e+001.56 / 5.483.5e-14+0.004 / +0.133
2026-05-19/episode_001right0.0e+002.97 / 6.302.4e-14+0.050 / +0.007
2026-05-19/episode_002left0.0e+001.69 / 4.632.9e-14+0.046 / -0.069
2026-05-19/episode_002right0.0e+001.78 / 6.453.5e-14+0.054 / +0.008
2026-05-19/episode_003left0.0e+001.50 / 3.361.8e-14-0.045 / +nan
2026-05-19/episode_003right0.0e+002.23 / 3.322.2e-14+0.023 / +0.106
2026-05-19/episode_004left0.0e+002.00 / 2.762.1e-14+0.004 / +0.397
2026-05-19/episode_004right0.0e+002.81 / 4.053.8e-14-0.213 / +0.278
2026-06-18/episode_000left0.0e+000.28 / 1.324.2e-14-0.067 / +0.073
2026-06-18/episode_000right0.0e+000.44 / 2.476.5e-14+0.080 / -0.003
2026-06-18/episode_001left0.0e+000.30 / 4.824.5e-14+0.050 / +0.087
2026-06-18/episode_001right0.0e+000.43 / 4.439.1e-14+0.066 / +0.051
2026-06-18/episode_002left0.0e+000.31 / 3.014.5e-14+0.080 / +0.064
2026-06-18/episode_002right0.0e+000.49 / 3.739.0e-14+0.113 / +0.017
2026-06-18/episode_003left0.0e+000.29 / 2.354.3e-14+0.107 / +0.101
2026-06-18/episode_003right0.0e+000.40 / 3.328.9e-14+0.080 / +0.040

Across all episodes: free-space invariance 0.0e+00 m, roundtrip error 9.1e-14 N (machine precision), max penetration 15.2 mm.

What the numbers say

Debug log (what actually went wrong)

foundfix
FEATS returns its no-contact output on every markerless frame switched force estimation to photometric-stereo depth (negative result kept above)
fixed dot-threshold 55 detects 0 dots (our dots bottom out at gray 56) percentile-based threshold — then made moot by the markerless finding
1.1 mm phantom depth at the image border (Poisson/Neumann edge artifact) exclude a 12/16-px margin from force integration
contact threshold exploded 30× when a reference frame was lightly touching median zero-map + MAD threshold instead of mean + std
4% single-frame force spikes (bad Poisson solves) median-3 over fresh frames only — row-wise filtering would see each duplicated value 3× and keep it
assumed gel normal [0,0,1] gave negative approach alignment at onsets use the rig's dual-ball calibrated gel_axis_in_rigid; sign verified against approach kinematics
fed the full camera view to a depth MLP trained on the SDK's 15%-cropped view same 1/7 border crop for React frames and FEATS images — geometry now identical between inference and validation
FEATS transfer started at ρ = 0.42: illumination tilt dwarfed real indentation, dot imprints leaked into depth per-frame robust background-plane removal + marker inpainting → ρ = 0.70; quadratic background tried and rejected (absorbed contact)
shear-loaded captures anti-correlate (ρ = −0.15) scoped the estimator to normal loading; documented as a limitation (React pushing involves shear — treat high-shear force estimates with caution)
theoretical Winkler scale off by ~40× (depth-unit + gel-constant assumptions) absolute scale fitted on FEATS ground truth; React forces in FEATS-calibrated newtons, uncertainty stated

Data: yxma/React · code ships with the dataset (preprocess/, toolbox/) · references: FARM (2510.13324), DexForce (2501.10356), FEATS (2411.03315), ACP (2410.09309), gsrobotics SDK.