Skip to content

Commit e656ba8

Browse files
committed
[FEATURE] Speed up and add noise options for tactile sensors: hysteresis, dead taxels, probe gain
1 parent c2f08c3 commit e656ba8

12 files changed

Lines changed: 4359 additions & 961 deletions

File tree

examples/sensors/tactile_sandbox.py

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
Interactive demo of tactile sensors on a fixed taxel pad (box or dome) with controllable objects.
3-
Sensor types: ContactDepthProbe, ElastomerTaxel, KinematicTaxel, ProximityTaxel.
3+
Sensor types: ContactDepthProbe, ContactProbe, ElastomerTaxel, KinematicTaxel, ProximityTaxel.
44
55
Note that the sensor readings here have not been calibrated to any units, and is purely for visualization purposes.
66
"""
@@ -24,9 +24,9 @@
2424
from genesis.engine.entities.rigid_entity import RigidEntity
2525
from genesis.engine.sensors.base_sensor import Sensor
2626

27-
KEY_DPOS = 0.005
27+
KEY_DPOS = 0.001
2828
FORCE_SCALE = 100.0
29-
ROT_FORCE_SCALE = 200.0
29+
ROT_FORCE_SCALE = 100.0
3030

3131
GRID_SIZE = 20 # 20x20 taxels for square
3232
PROBE_RADIUS = 0.004
@@ -48,13 +48,23 @@ def _add_tactile_sensor(
4848
probe_local_pos: np.ndarray,
4949
probe_normal: tuple[float, float, float] | np.ndarray,
5050
track_link_idx: tuple[int, ...],
51+
noise: bool,
5152
) -> "Sensor":
5253
common = dict(
5354
entity_idx=entity.idx,
5455
link_idx_local=link_idx_local,
5556
draw_debug=True,
5657
probe_radius=PROBE_RADIUS,
5758
)
59+
if noise:
60+
# Sensor imperfections shared by every tactile sensor type: viscoelastic hysteresis on the measured
61+
# branch, a noised sensing radius, and a per-taxel measured-branch depth gain.
62+
common.update(
63+
hysteresis_strength=0.5, # viscoelastic overshoot fraction
64+
hysteresis_tau=0.1, # viscoelastic relaxation time constant (seconds)
65+
probe_radius_noise=0.001, # additive sensing-radius noise (meters)
66+
probe_gain=1.5, # per-taxel measured-branch depth gain
67+
)
5868
if sensor_type == "elastomer":
5969
return scene.add_sensor(
6070
gs.sensors.ElastomerTaxel(
@@ -63,7 +73,8 @@ def _add_tactile_sensor(
6373
track_link_idx=track_link_idx,
6474
n_sample_points=2000,
6575
dilate_scale=1.0,
66-
shear_scale=100.0,
76+
shear_scale=2.0,
77+
normal_exponent=1.0,
6778
**common,
6879
)
6980
)
@@ -76,32 +87,44 @@ def _add_tactile_sensor(
7687
**common,
7788
)
7889
)
90+
if sensor_type == "contact":
91+
# Schmitt-trigger thresholds (contact depth in meters): a taxel latches on above contact_threshold and
92+
# only releases once the depth drops back below the lower release_threshold.
93+
return scene.add_sensor(
94+
gs.sensors.ContactProbe(
95+
probe_local_pos=probe_local_pos,
96+
contact_threshold=0.004,
97+
release_threshold=0.002,
98+
**common,
99+
)
100+
)
79101
if sensor_type == "kinematic":
80102
return scene.add_sensor(
81103
gs.sensors.KinematicTaxel(
82104
probe_local_pos=probe_local_pos,
83105
probe_local_normal=probe_normal,
84106
normal_stiffness=500.0,
85107
normal_damping=1.0,
86-
shear_scalar=5.0,
87-
twist_scalar=5.0,
108+
shear_scalar=4.0,
109+
twist_scalar=4.0,
88110
normal_exponent=1.5,
89111
**common,
90112
)
91113
)
92114

93-
common["probe_radius"] = PROBE_RADIUS * 2
94-
common["debug_point_cloud_radius"] = 0.001
115+
common["probe_radius"] = PROBE_RADIUS * 5
95116
if sensor_type == "proximity":
96117
return scene.add_sensor(
97118
gs.sensors.ProximityTaxel(
98119
probe_local_pos=probe_local_pos,
99120
track_link_idx=track_link_idx,
100121
n_sample_points=4000,
101-
stiffness=200.0,
102-
shear_coupling=100.0,
122+
stiffness=40.0,
123+
shear_coupling=10.0,
103124
probe_local_normal=probe_normal,
104-
probe_radius_noise=0.0001,
125+
debug_point_cloud_radius=0.0005,
126+
debug_probe_color=(0.2, 0.6, 1.0),
127+
debug_contact_color=(1.0, 0.2, 0.2),
105128
**common,
106129
)
107130
)
@@ -111,7 +134,6 @@ def _add_tactile_sensor(
111134
def _plot_tactile_sensor(
112135
scene: gs.Scene,
113136
sensor_type: str,
114-
labels: tuple[str, ...],
115137
sensors: "tuple[Sensor, ...]",
116138
n_envs: int = 1,
117139
plot_normal: tuple[float, float, float] = (0.0, 0.0, -1.0),
@@ -122,24 +144,24 @@ def _plot_tactile_sensor(
122144

123145
if sensor_type == "elastomer":
124146
for env_idx in range(n_envs):
125-
for label, sensor in zip(labels, sensors):
147+
for sensor in sensors:
126148
scene.start_recording(
127149
lambda s=sensor, i=env_idx: s.read()[i],
128150
gs.recorders.MPLVectorFieldPlot(
129-
title=f"({label} {OBJ_PER_ENV_LABELS[env_idx]}) ElastomerTaxel marker displacements",
151+
title=f"({OBJ_PER_ENV_LABELS[env_idx]}) ElastomerTaxel marker displacements",
130152
positions=sensor.probe_local_pos.reshape(-1, 3),
131153
normal=plot_normal,
132-
scale_factor=1.0,
133-
max_magnitude=0.01,
154+
scale_factor=0.1,
155+
max_magnitude=0.1,
134156
),
135157
)
136158
elif sensor_type == "kinematic":
137159
for env_idx in range(n_envs):
138-
for label, sensor in zip(labels, sensors):
160+
for sensor in sensors:
139161
scene.start_recording(
140162
lambda s=sensor, i=env_idx: s.read().force[i],
141163
gs.recorders.MPLVectorFieldPlot(
142-
title=f"({label} {OBJ_PER_ENV_LABELS[env_idx]}) KinematicTaxel force",
164+
title=f"({OBJ_PER_ENV_LABELS[env_idx]}) KinematicTaxel force",
143165
positions=sensor.probe_local_pos.reshape(-1, 3),
144166
normal=plot_normal,
145167
scale_factor=0.01,
@@ -148,14 +170,14 @@ def _plot_tactile_sensor(
148170
)
149171
elif sensor_type == "proximity":
150172
for env_idx in range(n_envs):
151-
for label, sensor in zip(labels, sensors):
173+
for sensor in sensors:
152174
scene.start_recording(
153175
lambda s=sensor, i=env_idx: s.read().force[i],
154176
gs.recorders.MPLVectorFieldPlot(
155-
title=f"({label} {OBJ_PER_ENV_LABELS[env_idx]}) ProximityTaxel force",
177+
title=f"({OBJ_PER_ENV_LABELS[env_idx]}) ProximityTaxel force",
156178
positions=sensor.probe_local_pos.reshape(-1, 3),
157179
normal=plot_normal,
158-
scale_factor=0.5,
180+
scale_factor=0.1,
159181
max_magnitude=1.0,
160182
),
161183
)
@@ -165,12 +187,22 @@ def _plot_tactile_sensor(
165187
lambda i=env_idx: tuple(sensor.read()[i].max() for sensor in sensors),
166188
gs.recorders.MPLLinePlot(
167189
title=f"ContactDepthProbe max depth ({OBJ_PER_ENV_LABELS[env_idx]})",
168-
labels=labels,
169190
x_label="step",
170191
y_label="depth",
171192
history_length=200,
172193
),
173194
)
195+
elif sensor_type == "contact":
196+
for env_idx in range(n_envs):
197+
scene.start_recording(
198+
lambda i=env_idx: tuple(sensor.read()[i].sum() for sensor in sensors),
199+
gs.recorders.MPLLinePlot(
200+
title=f"ContactProbe taxels in contact ({OBJ_PER_ENV_LABELS[env_idx]})",
201+
x_label="step",
202+
y_label="# taxels",
203+
history_length=200,
204+
),
205+
)
174206

175207

176208
def _print_sensor_reading(sensor_type: str, sensor: "Sensor", t: float) -> None:
@@ -183,6 +215,10 @@ def _print_sensor_reading(sensor_type: str, sensor: "Sensor", t: float) -> None:
183215
max_depth = data.max()
184216
if max_depth > gs.EPS:
185217
print(f"t={t:.2f}s max depth={max_depth:.4f}")
218+
elif sensor_type == "contact":
219+
n_contact = int(data.sum())
220+
if n_contact > 0:
221+
print(f"t={t:.2f}s taxels in contact={n_contact}")
186222
elif sensor_type == "kinematic":
187223
magnitude = torch.linalg.norm(data.force, axis=-1).max()
188224
if magnitude > gs.EPS:
@@ -204,10 +240,15 @@ def main() -> None:
204240
parser.add_argument("--dome", action="store_true", help="Change the sensor object to a dome instead of a box")
205241
parser.add_argument(
206242
"--sensor",
207-
choices=("elastomer", "depth", "kinematic", "proximity"),
243+
choices=("elastomer", "depth", "contact", "kinematic", "proximity"),
208244
default="elastomer",
209245
help="Type of tactile sensor to use.",
210246
)
247+
parser.add_argument(
248+
"--noise",
249+
action="store_true",
250+
help="Enable sensor imperfections (viscoelastic hysteresis, probe_radius_noise, probe_gain).",
251+
)
211252
args = parser.parse_args()
212253

213254
gs.init(
@@ -276,17 +317,17 @@ def main() -> None:
276317
ny=GRID_SIZE,
277318
)
278319

279-
# Procedural torus written to a temp .obj (avoids checking a 2k-line mesh into the repo).
280320
torus_path = os.path.join(tempfile.gettempdir(), "tactile_sandbox_torus.obj")
281321
if not os.path.exists(torus_path):
282-
trimesh.creation.torus(major_radius=0.3, minor_radius=0.1).export(torus_path)
322+
trimesh.creation.torus(major_radius=1.0, minor_radius=0.5).export(torus_path)
283323

284324
obj = scene.add_entity(
285325
morph=[
286326
gs.morphs.Mesh(
287327
file=torus_path,
288-
euler=(90.0, 0.0, 0.0),
289-
scale=OBJECT_SIZE,
328+
euler=(0.0, 0.0, 0.0),
329+
scale=OBJECT_SIZE * 2,
330+
convexify=False,
290331
),
291332
gs.morphs.Sphere(
292333
radius=OBJECT_SIZE / 2,
@@ -314,9 +355,10 @@ def main() -> None:
314355
probe_local_pos,
315356
probe_normal,
316357
track_link_idx=(obj.base_link_idx,),
358+
noise=args.noise,
317359
)
318360
if args.vis and "PYTEST_VERSION" not in os.environ:
319-
_plot_tactile_sensor(scene, args.sensor, ("",), (sensor,), n_envs=4, plot_normal=probe_normal_axis)
361+
_plot_tactile_sensor(scene, args.sensor, (sensor,), n_envs=4, plot_normal=probe_normal_axis)
320362
scene.build(n_envs=4, env_spacing=(SENSOR_OBJ_SIZE * 1.2, SENSOR_OBJ_SIZE * 1.2))
321363

322364
obj_init_pos = tensor_to_array(obj.get_pos())
@@ -382,7 +424,7 @@ def rotate(axis_idx: int, is_negative: bool):
382424
print("\n=== Tactile Sensor Sandbox ===")
383425
n_taxels = probe_local_pos.reshape(-1, 3).shape[0]
384426
layout = f"dome ({GRID_SIZE} latitude rings)" if args.dome else f"plane grid {probe_local_pos.shape[:-1]}"
385-
print(f"sensor={args.sensor}; taxels={n_taxels}; {layout}")
427+
print(f"sensor={args.sensor}; taxels={n_taxels}; {layout}; noise={'on' if args.noise else 'off'}")
386428
if args.vis and IS_MATPLOTLIB_AVAILABLE:
387429
print("Matplotlib live plot enabled when supported.")
388430
if args.vis:

0 commit comments

Comments
 (0)