Skip to content

Commit 0ea3e86

Browse files
committed
Add option to log F/T sensor data
1 parent 574f51e commit 0ea3e86

File tree

4 files changed

+102
-16
lines changed

4 files changed

+102
-16
lines changed

poetry.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ readme = "README.md"
77

88
[tool.poetry.dependencies]
99
python = ">=3.10,<3.11"
10-
# drake = {version = ">=0.0.20240409", source = "drake-nightly"}
10+
# drake = {version = ">=0.0.20240302", source = "drake-nightly"}
1111
drake = "^1.28.0"
1212
numpy = "^1.25.2"
13-
manipulation = {git = "https://github.com/RussTedrake/manipulation.git", rev = "a2d393fdad5e6079db53b0c4e749e2a7fbc935b0"}
14-
iiwa_setup = {git = "https://github.com/nepfaff/iiwa_setup.git", rev="f0d5bbfc8378313bb88721e13cb909928e9ff31f"}
13+
manipulation = {git = "https://github.com/RussTedrake/manipulation.git", rev = "dd7851d2a8af76462a37f20604886872f56be5b0"}
14+
iiwa_setup = {git = "https://github.com/nepfaff/iiwa_setup.git", rev="5d1ae518e049443f97d088d9c8e787829ef073b9"}
1515
tqdm = "^4.66.1"
1616
hydra-core = "^1.3.2"
1717
omegaconf = "^2.3.0"

robot_payload_id/utils/dataclasses.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,33 @@ class JointData:
4545
ft_sensor_measurements: Optional[np.ndarray] = None
4646
"""The force-torque sensor measurements of shape (T, 6) and form
4747
[f_x, f_y, f_z, tau_x, tau_y, tau_z]."""
48+
ft_sensor_sample_times_s: Optional[np.ndarray] = None
49+
"""The sample times in seconds of the force-torque sensor measurements of shape
50+
(T,)."""
4851

4952
def remove_duplicate_samples(self) -> "JointData":
5053
"""
5154
Removes duplicate samples from the joint data. Returns a new JointData object
5255
rather than modifying the current one.
5356
"""
5457
_, unique_indices = np.unique(self.sample_times_s, return_index=True)
55-
return JointData(
58+
joint_data = JointData(
5659
joint_positions=self.joint_positions[unique_indices],
5760
joint_velocities=self.joint_velocities[unique_indices],
5861
joint_accelerations=self.joint_accelerations[unique_indices],
5962
joint_torques=self.joint_torques[unique_indices],
6063
sample_times_s=self.sample_times_s[unique_indices],
61-
ft_sensor_measurements=(
62-
None
63-
if self.ft_sensor_measurements is None
64-
else self.ft_sensor_measurements[unique_indices]
65-
),
6664
)
65+
if self.ft_sensor_sample_times_s is not None:
66+
_, unique_ft_indices = np.unique(
67+
self.ft_sensor_sample_times_s, return_index=True
68+
)
69+
joint_data.ft_sensor_measurements = self.ft_sensor_measurements[
70+
unique_ft_indices
71+
]
72+
joint_data.ft_sensor_sample_times_s = self.ft_sensor_sample_times_s[
73+
unique_ft_indices
74+
]
6775

6876
def save_to_disk(self, path: Path) -> None:
6977
"""Saves the joint data to disk.
@@ -79,6 +87,10 @@ def save_to_disk(self, path: Path) -> None:
7987
np.save(path / "sample_times_s.npy", self.sample_times_s)
8088
if self.ft_sensor_measurements is not None:
8189
np.save(path / "ft_sensor_measurements.npy", self.ft_sensor_measurements)
90+
if self.ft_sensor_sample_times_s is not None:
91+
np.save(
92+
path / "ft_sensor_sample_times_s.npy", self.ft_sensor_sample_times_s
93+
)
8294

8395
@classmethod
8496
def load_from_disk(cls, path: Path) -> "JointData":
@@ -91,6 +103,7 @@ def load_from_disk(cls, path: Path) -> "JointData":
91103
The joint data.
92104
"""
93105
ft_sensor_measurements_path = path / "ft_sensor_measurements.npy"
106+
ft_sensor_sample_times_path = path / "ft_sensor_sample_times_s.npy"
94107
return cls(
95108
joint_positions=np.load(path / "joint_positions.npy"),
96109
joint_velocities=np.load(path / "joint_velocities.npy"),
@@ -102,6 +115,11 @@ def load_from_disk(cls, path: Path) -> "JointData":
102115
if ft_sensor_measurements_path.exists()
103116
else None
104117
),
118+
ft_sensor_sample_times_s=(
119+
np.load(ft_sensor_sample_times_path)
120+
if ft_sensor_sample_times_path.exists()
121+
else None
122+
),
105123
)
106124

107125
@classmethod

scripts/collect_joint_data.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@
66

77
import numpy as np
88

9+
# export PYTHONPATH=~/src/ft_300s_driver/bazel-bin/lcmtypes/ft_300s/:${PYTHONPATH}
10+
try:
11+
from ft_reading_t import ft_reading_t
12+
except ImportError:
13+
logging.warning("Failed to import `ft_reading_t`. Won't be able to log FT data.")
14+
915
from iiwa_setup.controllers import (
1016
InverseDynamicsControllerWithGravityCompensationCancellation,
1117
)
1218
from iiwa_setup.iiwa import IiwaHardwareStationDiagram
19+
from iiwa_setup.sensors import FTSensorDataReceiver
1320
from manipulation.station import LoadScenario
1421
from pydrake.all import (
1522
ApplySimulatorConfig,
@@ -18,6 +25,9 @@
1825
ConstantVectorSource,
1926
Demultiplexer,
2027
DiagramBuilder,
28+
DrakeLcm,
29+
LcmInterfaceSystem,
30+
LcmSubscriberSystem,
2131
MeshcatVisualizer,
2232
PiecewisePolynomial,
2333
Simulator,
@@ -107,6 +117,11 @@ def main():
107117
default=1e-3,
108118
help="The period at which to log data.",
109119
)
120+
parser.add_argument(
121+
"--log_ft_data",
122+
action="store_true",
123+
help="Whether to log the FT sensor data.",
124+
)
110125
parser.add_argument(
111126
"--html_path",
112127
type=Path,
@@ -132,6 +147,7 @@ def main():
132147
duration_to_remove_at_start = args.duration_to_remove_at_start
133148
noise_scale = args.noise_scale
134149
logging_period = args.logging_period
150+
log_ft_data = args.log_ft_data
135151
html_path = args.html_path
136152

137153
assert not (use_hardware and noise_scale > 0.0)
@@ -251,6 +267,29 @@ def main():
251267
wsg_const_pos_source.get_output_port(), station.GetInputPort("wsg.position")
252268
)
253269

270+
if log_ft_data:
271+
lcm = DrakeLcm()
272+
lcm_system = builder.AddNamedSystem(
273+
"lcm_interface_system", LcmInterfaceSystem(lcm)
274+
)
275+
ft_sensor_subscriber: LcmSubscriberSystem = builder.AddNamedSystem(
276+
"ft_sensor_subscriber",
277+
LcmSubscriberSystem.Make(
278+
channel="FT300S_SENSOR_DATA",
279+
lcm_type=ft_reading_t,
280+
lcm=lcm_system,
281+
use_cpp_serializer=False,
282+
wait_for_message_on_initialization_timeout=10,
283+
),
284+
)
285+
ft_sensor_data_receiver: FTSensorDataReceiver = builder.AddNamedSystem(
286+
"ft_sensor_data_receiver", FTSensorDataReceiver()
287+
)
288+
builder.Connect(
289+
ft_sensor_subscriber.get_output_port(),
290+
ft_sensor_data_receiver.get_input_port(),
291+
)
292+
254293
# Add data loggers
255294
desired_state_demux = builder.AddNamedSystem(
256295
"desired_state_demux", Demultiplexer(output_ports_sizes=[7, 7])
@@ -287,6 +326,12 @@ def main():
287326
"measured_torque_logger",
288327
VectorLogSink(num_positions, publish_period=logging_period),
289328
)
329+
if log_ft_data:
330+
# The FT 300-S sensor publishes at 100Hz.
331+
ft_logger: VectorLogSink = builder.AddNamedSystem(
332+
"ft_logger",
333+
VectorLogSink(input_size=6, publish_period=np.min([1e-2, logging_period])),
334+
)
290335
builder.Connect(
291336
desired_state_demux.get_output_port(0),
292337
commanded_position_logger.get_input_port(),
@@ -315,6 +360,11 @@ def main():
315360
station.GetOutputPort("iiwa.torque_measured"),
316361
measured_torque_logger.get_input_port(),
317362
)
363+
if log_ft_data:
364+
builder.Connect(
365+
ft_sensor_data_receiver.GetOutputPort("ft_measured"),
366+
ft_logger.get_input_port(),
367+
)
318368

319369
visualizer = MeshcatVisualizer.AddToBuilder(
320370
builder, station.GetOutputPort("query_object"), station.internal_meshcat
@@ -377,6 +427,10 @@ def main():
377427
simulator.get_context()
378428
).sample_times()
379429

430+
if log_ft_data:
431+
ft_data = ft_logger.FindLog(simulator.get_context()).data().T
432+
ft_sample_times_s = ft_logger.FindLog(simulator.get_context()).sample_times()
433+
380434
if only_log_excitation_traj_data:
381435
# Only keep data during excitation trajectory execution
382436
data_start_time = (
@@ -412,6 +466,14 @@ def main():
412466
# Shift sample times to start at 0
413467
sample_times_s -= sample_times_s[0]
414468

469+
if log_ft_data:
470+
ft_start_idx = np.argmax(ft_sample_times_s >= data_start_time)
471+
ft_end_idx = np.argmax(ft_sample_times_s >= excitation_traj_end_time)
472+
ft_data = ft_data[ft_start_idx:ft_end_idx]
473+
ft_sample_times_s = ft_sample_times_s[ft_start_idx:ft_end_idx]
474+
# Shift sample times to start at 0
475+
ft_sample_times_s -= ft_sample_times_s[0]
476+
415477
# Remove duplicated samples
416478
_, unique_indices = np.unique(sample_times_s, return_index=True)
417479
commanded_position_data = commanded_position_data[unique_indices]
@@ -422,6 +484,10 @@ def main():
422484
commanded_torque_data = commanded_torque_data[unique_indices]
423485
measured_torque_data = measured_torque_data[unique_indices]
424486
sample_times_s = sample_times_s[unique_indices]
487+
if log_ft_data:
488+
_, ft_unique_indices = np.unique(ft_sample_times_s, return_index=True)
489+
ft_data = ft_data[ft_unique_indices]
490+
ft_sample_times_s = ft_sample_times_s[ft_unique_indices]
425491

426492
# Add noise
427493
if noise_scale > 0.0:
@@ -446,6 +512,8 @@ def main():
446512
joint_accelerations=np.zeros_like(measured_position_data) * np.nan,
447513
joint_torques=measured_torque_data,
448514
sample_times_s=sample_times_s,
515+
ft_sensor_measurements=ft_data if log_ft_data else None,
516+
ft_sensor_sample_times_s=ft_sample_times_s if log_ft_data else None,
449517
)
450518
joint_data.save_to_disk(save_data_path)
451519

0 commit comments

Comments
 (0)