11"""
22Interactive 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
55Note that the sensor readings here have not been calibrated to any units, and is purely for visualization purposes.
66"""
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
2828FORCE_SCALE = 100.0
29- ROT_FORCE_SCALE = 200 .0
29+ ROT_FORCE_SCALE = 100 .0
3030
3131GRID_SIZE = 20 # 20x20 taxels for square
3232PROBE_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(
111134def _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
176208def _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