markerSizeProvider)
+ {
+ resultsToPublish = new OpenCVArUcoMarkerDetectionResults();
+ publisher = new OpenCVArUcoMarkerROS2Publisher(resultsToPublish, ros2, markerSizeProvider, sensorFrame.getReferenceFrame());
+ return this;
+ }
+
+ @Override
+ protected void runTask()
+ {
+ try
+ { // Get an image
+ imageSensor.waitForGrab();
+ RawImage colorImage = imageSensor.getImage(colorImageKey);
+
+ if (firstRun)
+ { // Set the camera intrinsics on first run
+ detector.setCameraIntrinsics(colorImage.getIntrinsicsCopy());
+ firstRun = false;
+ }
+
+ // Update the sensor frame
+ sensorFrame.update(transformToWorld -> transformToWorld.set(colorImage.getPose()));
+
+ // Detect markers
+ detector.update(colorImage.getCpuImageMat());
+ resultSwapReference.getForThreadOne().copyOutputData(detector);
+ resultSwapReference.swap();
+
+ if (publisher != null)
+ { // Publish results if enabled
+ resultsToPublish.copyOutputData(detector);
+ publisher.update();
+ }
+
+ colorImage.release();
+ } catch (InterruptedException ignored) {}
+ }
+
+ /**
+ * Get the result swap reference.
+ *
+ * This is thread one, and you are thread two.
+ * Synchronize over the swap reference while using the results.
+ * Do not call {@link SwapReference#swap()} (this thread takes care of that for you).
+ *
+ * @return A swap reference to the latest results.
+ */
+ public SwapReference getResultSwapReference()
+ {
+ return resultSwapReference;
+ }
+
+ public ReferenceFrame getSensorFrame()
+ {
+ return sensorFrame.getReferenceFrame();
+ }
+
+ @Override
+ public void kill()
+ {
+ super.kill();
+ interrupt();
+ }
+}
diff --git a/ihmc-perception/src/main/java/us/ihmc/perception/rapidRegions/RapidPlanarRegionsExtractionThread.java b/ihmc-perception/src/main/java/us/ihmc/perception/rapidRegions/RapidPlanarRegionsExtractionThread.java
new file mode 100644
index 00000000000..3bf4230ff42
--- /dev/null
+++ b/ihmc-perception/src/main/java/us/ihmc/perception/rapidRegions/RapidPlanarRegionsExtractionThread.java
@@ -0,0 +1,112 @@
+package us.ihmc.perception.rapidRegions;
+
+import org.bytedeco.opencl.global.OpenCL;
+import us.ihmc.commons.thread.RepeatingTaskThread;
+import us.ihmc.commons.thread.TypedNotification;
+import us.ihmc.communication.PerceptionAPI;
+import us.ihmc.communication.property.ROS2StoredPropertySet;
+import us.ihmc.communication.ros2.ROS2PublishSubscribeAPI;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.perception.BytedecoImage;
+import us.ihmc.perception.RawImage;
+import us.ihmc.perception.comms.PerceptionComms;
+import us.ihmc.perception.opencl.OpenCLManager;
+import us.ihmc.perception.tools.PerceptionMessageTools;
+import us.ihmc.robotics.geometry.FramePlanarRegionsList;
+import us.ihmc.robotics.referenceFrames.MutableReferenceFrame;
+import us.ihmc.sensors.ImageSensor;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class RapidPlanarRegionsExtractionThread extends RepeatingTaskThread
+{
+ private static final double UPDATE_FREQUENCY = 10.0;
+
+ private final ROS2PublishSubscribeAPI ros2;
+
+ private final OpenCLManager openCLManager;
+
+ private final ImageSensor imageSensor;
+ private final int depthImageKey;
+ private final MutableReferenceFrame sensorFrame;
+
+ private RapidPlanarRegionsExtractor extractor;
+ private ROS2StoredPropertySet extractorParametersSync;
+ private final FramePlanarRegionsList framePlanarRegions = new FramePlanarRegionsList();
+ private final Set> newPlanarRegionNotifications = Collections.newSetFromMap(new ConcurrentHashMap<>());
+
+ public RapidPlanarRegionsExtractionThread(ROS2PublishSubscribeAPI ros2, OpenCLManager openCLManager, ImageSensor imageSensor, int depthImageKey)
+ {
+ super(imageSensor.getSensorName() + RapidPlanarRegionsExtractionThread.class.getSimpleName());
+ setFrequencyLimit(UPDATE_FREQUENCY);
+
+ this.ros2 = ros2;
+ this.openCLManager = openCLManager;
+ this.imageSensor = imageSensor;
+ this.depthImageKey = depthImageKey;
+
+ sensorFrame = new MutableReferenceFrame("PlanarRegionExtractionSensorFrame", ReferenceFrame.getWorldFrame());
+ }
+
+ @Override
+ protected void runTask()
+ {
+ // Get an image from the sensor
+ RawImage depthImage = imageSensor.getImage(depthImageKey);
+ if (depthImage == null)
+ return;
+
+ // Initialize if not yet initialized
+ if (extractor == null)
+ initialize(depthImage);
+
+ // Update parameters
+ extractorParametersSync.updateAndPublishThrottledStatus();
+
+ // Update the sensor frame using the depth image pose
+ sensorFrame.update(transformToWorld -> transformToWorld.set(depthImage.getOrientation(), depthImage.getPosition()));
+
+ // Extract the planar regions
+ BytedecoImage bytedecoImage = new BytedecoImage(depthImage.getCpuImageMat().clone());
+ bytedecoImage.createOpenCLImage(openCLManager, OpenCL.CL_MEM_READ_WRITE);
+ extractor.update(bytedecoImage, sensorFrame.getReferenceFrame(), framePlanarRegions);
+ extractor.setProcessing(false);
+ bytedecoImage.destroy(openCLManager);
+
+ // Give a copy to notifications
+ FramePlanarRegionsList planarRegionsCopy = framePlanarRegions.copy();
+ for (TypedNotification notification : newPlanarRegionNotifications)
+ notification.set(planarRegionsCopy);
+
+ // Publish the frame planar regions
+ PerceptionMessageTools.publishFramePlanarRegionsList(framePlanarRegions, PerceptionAPI.PERSPECTIVE_RAPID_REGIONS, ros2);
+
+ depthImage.release();
+ }
+
+ private void initialize(RawImage depthImage)
+ {
+ extractor = new RapidPlanarRegionsExtractor(openCLManager, depthImage.getIntrinsicsCopy());
+ extractor.getDebugger().setEnabled(false);
+
+ extractorParametersSync = new ROS2StoredPropertySet<>(ros2, PerceptionComms.PERSPECTIVE_RAPID_REGION_PARAMETERS, extractor.getParameters());
+ }
+
+ public TypedNotification getNewPlanarRegionsNotification()
+ {
+ TypedNotification notification = new TypedNotification<>();
+ newPlanarRegionNotifications.add(notification);
+ return notification;
+ }
+
+ @Override
+ public void kill()
+ {
+ super.kill();
+
+ if (extractor != null)
+ extractor.destroy();
+ }
+}
diff --git a/ihmc-perception/src/main/java/us/ihmc/perception/realsense/RealsenseDeviceManager.java b/ihmc-perception/src/main/java/us/ihmc/perception/realsense/RealsenseDeviceManager.java
index 725ff5d9f77..c790ca9b6ba 100644
--- a/ihmc-perception/src/main/java/us/ihmc/perception/realsense/RealsenseDeviceManager.java
+++ b/ihmc-perception/src/main/java/us/ihmc/perception/realsense/RealsenseDeviceManager.java
@@ -27,7 +27,7 @@ public class RealsenseDeviceManager
private final YoDouble numberOfDevices = new YoDouble("numberOfDevices", registry);
private final rs2_context context;
- private final rs2_device_list devices;
+ private rs2_device_list devices;
private final rs2_error error = new rs2_error();
public RealsenseDeviceManager()
@@ -45,9 +45,6 @@ public RealsenseDeviceManager(YoRegistry parentRegistry, YoGraphicsListRegistry
context = realsense2.rs2_create_context(realsense2.RS2_API_VERSION, error);
checkError();
- devices = realsense2.rs2_query_devices(context, error);
- checkError();
-
updateDeviceCount();
if (parentRegistry != null)
@@ -139,6 +136,10 @@ private String getDeviceInfo(rs2_device rs2Device, int deviceInfoEnum)
private int updateDeviceCount()
{
+ realsense2.rs2_delete_device_list(devices);
+ devices = realsense2.rs2_query_devices(context, error);
+ checkError();
+
int rs2DeviceCount = realsense2.rs2_get_device_count(devices, error);
checkError();
diff --git a/ihmc-perception/src/main/java/us/ihmc/perception/sceneGraph/ros2/ROS2SceneGraphUpdateThread.java b/ihmc-perception/src/main/java/us/ihmc/perception/sceneGraph/ros2/ROS2SceneGraphUpdateThread.java
new file mode 100644
index 00000000000..b35a91539b3
--- /dev/null
+++ b/ihmc-perception/src/main/java/us/ihmc/perception/sceneGraph/ros2/ROS2SceneGraphUpdateThread.java
@@ -0,0 +1,83 @@
+package us.ihmc.perception.sceneGraph.ros2;
+
+import us.ihmc.commons.thread.RepeatingTaskThread;
+import us.ihmc.commons.thread.TypedNotification;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.perception.detections.DetectionManager;
+import us.ihmc.perception.opencv.OpenCVArUcoMarkerDetectionResults;
+import us.ihmc.perception.opencv.OpenCVArUcoMarkerDetectionThread;
+import us.ihmc.perception.sceneGraph.SceneGraph;
+import us.ihmc.perception.sceneGraph.SceneNode;
+import us.ihmc.perception.sceneGraph.arUco.ArUcoSceneTools;
+import us.ihmc.perception.sceneGraph.rigidBody.doors.DoorNode;
+import us.ihmc.robotics.geometry.FramePlanarRegionsList;
+import us.ihmc.robotics.geometry.PlanarRegionsList;
+import us.ihmc.tools.thread.SwapReference;
+
+import java.util.function.Supplier;
+
+public class ROS2SceneGraphUpdateThread extends RepeatingTaskThread
+{
+ private final ROS2SceneGraph sceneGraph;
+ private final DetectionManager detectionManager;
+ private final Supplier robotPelvisFrameSupplier;
+
+ private TypedNotification planarRegionsNotification;
+ private OpenCVArUcoMarkerDetectionThread arUcoDetectionThread;
+
+ public ROS2SceneGraphUpdateThread(ROS2SceneGraph sceneGraph,
+ DetectionManager detectionManager,
+ Supplier robotPelvisFrameSupplier)
+ {
+ super(ROS2SceneGraphUpdateThread.class.getSimpleName());
+ setFrequencyLimit(SceneGraph.UPDATE_FREQUENCY);
+ this.sceneGraph = sceneGraph;
+ this.detectionManager = detectionManager;
+ this.robotPelvisFrameSupplier = robotPelvisFrameSupplier;
+ }
+
+ public void setPlanarRegionsNotification(TypedNotification planarRegionsNotification)
+ {
+ this.planarRegionsNotification = planarRegionsNotification;
+ }
+
+ public void setArUcoDetectionThread(OpenCVArUcoMarkerDetectionThread arUcoDetectionThread)
+ {
+ this.arUcoDetectionThread = arUcoDetectionThread;
+ }
+
+ @Override
+ protected void runTask()
+ {
+ sceneGraph.updateSubscription();
+
+ if (arUcoDetectionThread != null)
+ {
+ SwapReference arUcoResultsReference = arUcoDetectionThread.getResultSwapReference();
+ synchronized (arUcoResultsReference)
+ {
+ ArUcoSceneTools.updateSceneGraph(arUcoResultsReference.getForThreadTwo(), arUcoDetectionThread.getSensorFrame(), sceneGraph);
+ }
+ }
+
+ sceneGraph.updateDetections(detectionManager);
+
+ // Update planar regions for door nodes
+ if (planarRegionsNotification != null && planarRegionsNotification.poll())
+ {
+ FramePlanarRegionsList framePlanarRegions = planarRegionsNotification.read();
+ PlanarRegionsList planarRegionsInWorldFrame = framePlanarRegions.getPlanarRegionsList();
+ planarRegionsInWorldFrame.applyTransform(framePlanarRegions.getSensorToWorldFrameTransform());
+
+ for (SceneNode sceneNode : sceneGraph.getSceneNodesByID())
+ {
+ if (sceneNode instanceof DoorNode doorNode)
+ doorNode.getDoorPanel().filterAndSetPlanarRegionFromPlanarRegionsList(planarRegionsInWorldFrame);
+ }
+ }
+
+ ReferenceFrame pelvisFrame = robotPelvisFrameSupplier.get();
+ sceneGraph.updateOnRobotOnly(pelvisFrame);
+ sceneGraph.updatePublication();
+ }
+}
diff --git a/ihmc-perception/src/main/java/us/ihmc/perception/streaming/ROS2SRTSensorStreamer.java b/ihmc-perception/src/main/java/us/ihmc/perception/streaming/ROS2SRTSensorStreamer.java
index 9e7063bb41d..e17e8b335e6 100644
--- a/ihmc-perception/src/main/java/us/ihmc/perception/streaming/ROS2SRTSensorStreamer.java
+++ b/ihmc-perception/src/main/java/us/ihmc/perception/streaming/ROS2SRTSensorStreamer.java
@@ -21,13 +21,20 @@
public class ROS2SRTSensorStreamer
{
private final ROS2Node ros2Node;
+ private boolean destroyROS2Node = false;
private final Map, ROS2SRTVideoStreamer> videoStreamers = new HashMap<>();
private final ExecutorService sendFrameExecutor = Executors.newCachedThreadPool();
public ROS2SRTSensorStreamer()
{
- ros2Node = new ROS2NodeBuilder().build(getClass().getSimpleName().toLowerCase() + "_node");
+ this(new ROS2NodeBuilder().build(ROS2SRTSensorStreamer.class.getSimpleName().toLowerCase() + "_node"));
+ destroyROS2Node = true;
+ }
+
+ public ROS2SRTSensorStreamer(ROS2Node ros2Node)
+ {
+ this.ros2Node = ros2Node;
}
public void addStream(ROS2Topic streamTopic,
@@ -51,7 +58,7 @@ public boolean hasStream(ROS2Topic streamTopic)
public void sendFrame(ROS2Topic streamTopic, RawImage frame)
{
- if (frame != null && frame.get() == null)
+ if (frame == null || frame.get() == null)
return;
if (!hasStream(streamTopic))
@@ -70,6 +77,9 @@ public void destroy()
for (ROS2SRTVideoStreamer videoStreamer : videoStreamers.values())
videoStreamer.destroy();
+
+ if (destroyROS2Node)
+ ros2Node.destroy();
}
private void addStreamWithGuessedParameters(ROS2Topic streamTopic, RawImage exampleImage)
diff --git a/ihmc-perception/src/main/java/us/ihmc/perception/streaming/SRTVideoStreamer.java b/ihmc-perception/src/main/java/us/ihmc/perception/streaming/SRTVideoStreamer.java
index b201591235c..2d102058790 100644
--- a/ihmc-perception/src/main/java/us/ihmc/perception/streaming/SRTVideoStreamer.java
+++ b/ihmc-perception/src/main/java/us/ihmc/perception/streaming/SRTVideoStreamer.java
@@ -217,6 +217,12 @@ public void destroy()
interruptCallback.interrupt();
}
+ if (encoder != null)
+ {
+ encoder.flush(this::writeToCallers);
+ encoder.destroy();
+ }
+
Iterator callerIterator = callers.iterator();
while (callerIterator.hasNext())
{
@@ -224,9 +230,6 @@ public void destroy()
callerIterator.remove();
}
- if (encoder != null)
- encoder.destroy();
-
interruptCallback.close();
}
diff --git a/ihmc-perception/src/main/java/us/ihmc/perception/tools/PerceptionMessageTools.java b/ihmc-perception/src/main/java/us/ihmc/perception/tools/PerceptionMessageTools.java
index 28c797f2762..8f143a89f74 100644
--- a/ihmc-perception/src/main/java/us/ihmc/perception/tools/PerceptionMessageTools.java
+++ b/ihmc-perception/src/main/java/us/ihmc/perception/tools/PerceptionMessageTools.java
@@ -106,9 +106,9 @@ public static void publishJPGCompressedColorImage(BytePointer compressedColorPoi
public static void publishFramePlanarRegionsList(FramePlanarRegionsList framePlanarRegionsList,
ROS2Topic topic,
- ROS2Helper ros2Helper)
+ ROS2PublishSubscribeAPI ros2)
{
- ros2Helper.publish(topic, PlanarRegionMessageConverter.convertToFramePlanarRegionsListMessage(framePlanarRegionsList));
+ ros2.publish(topic, PlanarRegionMessageConverter.convertToFramePlanarRegionsListMessage(framePlanarRegionsList));
}
public static void publishPlanarRegionsList(PlanarRegionsList planarRegionsList, ROS2Topic topic, ROS2Helper ros2Helper)
diff --git a/ihmc-perception/src/main/java/us/ihmc/sensors/ImageSensor.java b/ihmc-perception/src/main/java/us/ihmc/sensors/ImageSensor.java
new file mode 100644
index 00000000000..4d3045d8537
--- /dev/null
+++ b/ihmc-perception/src/main/java/us/ihmc/sensors/ImageSensor.java
@@ -0,0 +1,131 @@
+package us.ihmc.sensors;
+
+import us.ihmc.commons.Conversions;
+import us.ihmc.commons.thread.RepeatingTaskThread;
+import us.ihmc.commons.thread.ThreadTools;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.perception.RawImage;
+
+import java.util.function.Supplier;
+
+public abstract class ImageSensor implements AutoCloseable
+{
+ private static final double SECONDS_BETWEEN_RETRIES = 1.0; // Wait 1 second between retries for starting sensors
+
+ private final String sensorName;
+ /** Sensor will be in world frame by default, unless a sensor frame supplier is specified through {@link #setSensorFrameSupplier(Supplier)}. */
+ protected volatile Supplier sensorFrameSupplier;
+
+ private final RepeatingTaskThread grabThread;
+ private final Object grabNotification = new Object();
+
+ public ImageSensor(String sensorName)
+ {
+ this.sensorName = sensorName;
+ grabThread = new RepeatingTaskThread(sensorName + "Grabber", this::grabAndNotify);
+ }
+
+ /**
+ * Initializes and starts the sensor.
+ * @return Whether the sensor was successfully initialized and started.
+ */
+ protected abstract boolean startSensor();
+
+ /**
+ * Set the sensor frame supplier.
+ * @param sensorFrameSupplier Supplier of the sensor's reference frame.
+ */
+ public void setSensorFrameSupplier(Supplier sensorFrameSupplier)
+ {
+ this.sensorFrameSupplier = sensorFrameSupplier;
+ }
+
+ /**
+ * @return Whether the sensor is running. Not necessarily the same as whether the grab thread is running.
+ */
+ public abstract boolean isSensorRunning();
+
+ /**
+ * Grab all images the sensor provides. Blocks until new images are available.
+ * @return Whether new images were successfully grabbed.
+ */
+ protected abstract boolean grab();
+
+ /**
+ *
+ * Get the latest image grabbed by the sensor, specifying the image to get using its key.
+ *
+ *
+ * This method should either return a new {@link RawImage}, such that the reference count = 1,
+ * or increment the reference count of the returned image by calling {@link RawImage#get()}.
+ * It is the caller's responsibility to call {@link RawImage#release()} after calling this method.
+ *
+ * @param imageKey Key of the image to get.
+ * @return A {@link RawImage} with an incremented reference count.
+ * The caller must call {@link RawImage#release()}.
+ */
+ public abstract RawImage getImage(int imageKey);
+
+ public String getSensorName()
+ {
+ return sensorName;
+ }
+
+ public synchronized void run(boolean run)
+ {
+ if (!grabThread.isAlive())
+ grabThread.start();
+
+ grabThread.setRepeating(run);
+ }
+
+ public void waitForGrab() throws InterruptedException
+ {
+ synchronized (grabNotification)
+ {
+ grabNotification.wait();
+ }
+ }
+
+ public void waitForGrab(double timeoutSeconds) throws InterruptedException
+ {
+ long millis = (long) Conversions.secondsToMilliseconds(timeoutSeconds);
+ long additionalNanos = Conversions.secondsToNanoseconds(timeoutSeconds) - Conversions.millisecondsToNanoseconds(millis);
+
+ synchronized (grabNotification)
+ {
+ grabNotification.wait(millis, (int) additionalNanos);
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ grabThread.blockingKill();
+ }
+
+ public RepeatingTaskThread getGrabThread()
+ {
+ return grabThread;
+ }
+
+ private void grabAndNotify()
+ {
+ // If the sensor is not running, try to start the sensor
+ if (!isSensorRunning() && !startSensor())
+ { // if sensor failed to start, sleep a bit and retry
+ ThreadTools.park(SECONDS_BETWEEN_RETRIES);
+ return;
+ }
+
+ // Grab the images
+ if (!grab())
+ return; // Grab failed, return
+
+ // Grab succeeded, notify threads waiting for new images
+ synchronized (grabNotification)
+ {
+ grabNotification.notifyAll();
+ }
+ }
+}
diff --git a/ihmc-perception/src/main/java/us/ihmc/sensors/RealsenseImageSensor.java b/ihmc-perception/src/main/java/us/ihmc/sensors/RealsenseImageSensor.java
new file mode 100644
index 00000000000..83eca7e2745
--- /dev/null
+++ b/ihmc-perception/src/main/java/us/ihmc/sensors/RealsenseImageSensor.java
@@ -0,0 +1,152 @@
+package us.ihmc.sensors;
+
+import org.bytedeco.opencv.global.opencv_core;
+import org.bytedeco.opencv.opencv_core.Mat;
+import us.ihmc.commons.thread.Throttler;
+import us.ihmc.euclid.referenceFrame.FramePose3D;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.log.LogTools;
+import us.ihmc.perception.RawImage;
+import us.ihmc.perception.realsense.RealsenseConfiguration;
+import us.ihmc.perception.realsense.RealsenseDevice;
+import us.ihmc.perception.realsense.RealsenseDeviceManager;
+
+import java.time.Instant;
+
+public class RealsenseImageSensor extends ImageSensor
+{
+ public static final int COLOR_IMAGE_KEY = 0;
+ public static final int DEPTH_IMAGE_KEY = 1;
+ public static final int OUTPUT_IMAGE_COUNT = 2;
+
+ private static final double OUTPUT_FREQUENCY = 20.0;
+
+ private final RealsenseConfiguration realsenseConfiguration;
+ private final RealsenseDeviceManager realsenseManager;
+ private RealsenseDevice realsense = null;
+
+ private final RawImage[] grabbedImages = new RawImage[OUTPUT_IMAGE_COUNT];
+ private long grabSequenceNumber = 0L;
+ private int grabFailureCount = 0;
+
+ private final FramePose3D depthPose = new FramePose3D();
+ private final FramePose3D colorPose = new FramePose3D();
+ private final Throttler grabThrottler = new Throttler().setFrequency(OUTPUT_FREQUENCY);
+
+ public RealsenseImageSensor(RealsenseDeviceManager realsenseManager, RealsenseConfiguration realsenseConfiguration)
+ {
+ super(realsenseConfiguration.name().split("_")[0]);
+
+ this.realsenseManager = realsenseManager;
+ this.realsenseConfiguration = realsenseConfiguration;
+ }
+
+ @Override
+ protected boolean startSensor()
+ {
+ if (realsense != null)
+ {
+ if (realsense.getDevice() != null)
+ realsense.deleteDevice();
+ realsense = null;
+ }
+
+ realsense = realsenseManager.createBytedecoRealsenseDevice(realsenseConfiguration);
+
+ boolean success = realsense != null && realsense.getDevice() != null;
+ if (success)
+ {
+ LogTools.info("Initializing Realsense...");
+ realsense.enableColor(realsenseConfiguration);
+ realsense.initialize();
+ grabFailureCount = 0;
+ }
+ else
+ {
+ LogTools.error("Failed to initialize Realsense");
+ }
+
+ return success;
+ }
+
+ @Override
+ public boolean isSensorRunning()
+ {
+ return realsense != null && realsense.getDevice() != null && grabFailureCount < OUTPUT_FREQUENCY;
+ }
+
+ @Override
+ protected boolean grab()
+ {
+ grabThrottler.waitAndRun();
+
+ // Update the sensor poses
+ ReferenceFrame sensorFrame = sensorFrameSupplier.get();
+ depthPose.setToZero(sensorFrame);
+ depthPose.changeFrame(ReferenceFrame.getWorldFrame());
+
+ colorPose.setIncludingFrame(sensorFrame, realsense.getDepthToColorTranslation(), realsense.getDepthToColorRotation());
+ colorPose.invert();
+ colorPose.changeFrame(ReferenceFrame.getWorldFrame());
+
+ // Read grabbed images
+ if (!realsense.readFrameData())
+ {
+ grabFailureCount++;
+ return false;
+ }
+ Instant grabTime = Instant.now();
+ grabSequenceNumber++;
+
+ // Create mats with the images
+ realsense.updateDataBytePointers();
+ Mat bgrImage = new Mat(realsense.getColorHeight(), realsense.getColorWidth(), opencv_core.CV_8UC3, realsense.getColorFrameData());
+ Mat depthImage = new Mat(realsense.getDepthHeight(), realsense.getDepthWidth(), opencv_core.CV_16UC1, realsense.getDepthFrameData());
+
+ // Update grabbed images
+ synchronized (grabbedImages)
+ {
+ grabbedImages[COLOR_IMAGE_KEY] = RawImage.createWithBGRImage(bgrImage,
+ realsense.getColorCameraIntrinsics(),
+ new FramePose3D(colorPose),
+ grabTime,
+ grabSequenceNumber);
+ grabbedImages[DEPTH_IMAGE_KEY] = RawImage.createWith16BitDepth(depthImage,
+ realsense.getDepthCameraIntrinsics(),
+ new FramePose3D(depthPose),
+ grabTime,
+ grabSequenceNumber,
+ (float) realsense.getDepthDiscretization());
+ }
+
+ grabFailureCount = 0;
+ return true;
+ }
+
+ @Override
+ public RawImage getImage(int imageKey)
+ {
+ synchronized (grabbedImages)
+ {
+ return new RawImage(grabbedImages[imageKey]);
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ System.out.println("Closing " + getClass().getSimpleName());
+ super.close();
+
+ // Release the images
+ for (RawImage image : grabbedImages)
+ if (image != null)
+ image.release();
+
+ // Close the camera
+ if (realsense != null && realsense.getDevice() != null)
+ realsense.deleteDevice();
+
+ System.out.println("Closed " + getClass().getSimpleName());
+ }
+}
diff --git a/ihmc-perception/src/main/java/us/ihmc/sensors/ZEDImageSensor.java b/ihmc-perception/src/main/java/us/ihmc/sensors/ZEDImageSensor.java
new file mode 100644
index 00000000000..2e9dc903ccc
--- /dev/null
+++ b/ihmc-perception/src/main/java/us/ihmc/sensors/ZEDImageSensor.java
@@ -0,0 +1,380 @@
+package us.ihmc.sensors;
+
+import org.bytedeco.javacpp.Pointer;
+import org.bytedeco.opencv.opencv_core.GpuMat;
+import us.ihmc.euclid.referenceFrame.FramePose3D;
+import us.ihmc.euclid.referenceFrame.ReferenceFrame;
+import us.ihmc.euclid.tuple3D.Vector3D;
+import us.ihmc.euclid.tuple4D.Quaternion;
+import us.ihmc.log.LogTools;
+import us.ihmc.perception.CameraModel;
+import us.ihmc.perception.RawImage;
+import us.ihmc.perception.camera.CameraIntrinsics;
+import us.ihmc.perception.imageMessage.PixelFormat;
+import us.ihmc.robotics.referenceFrames.MutableReferenceFrame;
+import us.ihmc.zed.SL_CalibrationParameters;
+import us.ihmc.zed.SL_CameraInformation;
+import us.ihmc.zed.SL_InitParameters;
+import us.ihmc.zed.SL_PositionalTrackingParameters;
+import us.ihmc.zed.SL_Quaternion;
+import us.ihmc.zed.SL_RuntimeParameters;
+import us.ihmc.zed.SL_Vector3;
+import us.ihmc.zed.library.ZEDJavaAPINativeLibrary;
+
+import java.time.Instant;
+
+import static us.ihmc.zed.global.zed.*;
+
+public class ZEDImageSensor extends ImageSensor
+{
+ static
+ {
+ ZEDJavaAPINativeLibrary.load();
+ }
+
+ public static final int LEFT_COLOR_IMAGE_KEY = 0;
+ public static final int RIGHT_COLOR_IMAGE_KEY = 1;
+ public static final int DEPTH_IMAGE_KEY = 2;
+
+ public static final int OUTPUT_IMAGE_COUNT = 3;
+
+ protected static final int CAMERA_FPS = 30;
+ private static final float MILLIMETER_TO_METERS = 0.001f;
+
+ private final int cameraID;
+ private final ZEDModelData zedModel;
+ private final int slInputType;
+
+ private final RawImage[] grabbedImages = new RawImage[OUTPUT_IMAGE_COUNT];
+ private final Pointer[] slMatPointers = new Pointer[OUTPUT_IMAGE_COUNT];
+ private final CameraIntrinsics leftSensorIntrinsics = new CameraIntrinsics();
+ private final CameraIntrinsics rightSensorIntrinsics = new CameraIntrinsics();
+ private int imageWidth;
+ private int imageHeight;
+
+ private float sensorCenterToCameraDistanceY = 0.0f;
+ private final FramePose3D leftSensorPose = new FramePose3D();
+ private final FramePose3D rightSensorPose = new FramePose3D();
+
+ private long grabSequenceNumber = 0L;
+ private Instant lastGrabTime;
+ private boolean lastGrabFailed = false;
+
+ protected final SL_InitParameters zedInitParameters = new SL_InitParameters();
+ protected final SL_RuntimeParameters zedRuntimeParameters = new SL_RuntimeParameters();
+
+ private boolean positionalTrackingEnabled = false;
+ private final MutableReferenceFrame trackedSensorFrame = new MutableReferenceFrame();
+ private final SL_Quaternion sensorRotation = new SL_Quaternion();
+ private final SL_Vector3 sensorTranslation = new SL_Vector3();
+
+ public ZEDImageSensor(int cameraID, ZEDModelData zedModel, int slInputType)
+ {
+ super(zedModel.name());
+
+ this.cameraID = cameraID;
+ this.zedModel = zedModel;
+ this.slInputType = slInputType;
+
+ // Set runtime parameters to default values
+ zedRuntimeParameters.reference_frame(SL_REFERENCE_FRAME_CAMERA);
+ zedRuntimeParameters.enable_depth(true);
+ zedRuntimeParameters.confidence_threshold(100);
+ zedRuntimeParameters.texture_confidence_threshold(100);
+ zedRuntimeParameters.remove_saturated_areas(true);
+ zedRuntimeParameters.enable_fill_mode(false);
+ }
+
+ @Override
+ protected boolean startSensor()
+ {
+ try
+ {
+ if (sl_is_opened(cameraID))
+ sl_close_camera(cameraID);
+
+ sl_create_camera(cameraID);
+
+ // Set the initialization parameters
+ setInitParameters(zedInitParameters);
+
+ // Open the camera
+ int returnCode = openCamera();
+ throwOnError(returnCode);
+
+ if (positionalTrackingEnabled)
+ {
+ SL_PositionalTrackingParameters positionalTrackingParameters = new SL_PositionalTrackingParameters();
+ sl_enable_positional_tracking(cameraID, positionalTrackingParameters, "");
+ }
+
+ // Get camera intrinsics
+ SL_CalibrationParameters sensorIntrinsics = sl_get_calibration_parameters(cameraID, false);
+ imageWidth = sl_get_width(cameraID);
+ imageHeight = sl_get_height(cameraID);
+
+ leftSensorIntrinsics.setWidth(imageWidth);
+ leftSensorIntrinsics.setHeight(imageHeight);
+ leftSensorIntrinsics.setFx(sensorIntrinsics.left_cam().fx());
+ leftSensorIntrinsics.setFy(sensorIntrinsics.left_cam().fy());
+ leftSensorIntrinsics.setCx(sensorIntrinsics.left_cam().cx());
+ leftSensorIntrinsics.setCy(sensorIntrinsics.left_cam().cy());
+
+ rightSensorIntrinsics.setWidth(imageWidth);
+ rightSensorIntrinsics.setHeight(imageHeight);
+ rightSensorIntrinsics.setFx(sensorIntrinsics.right_cam().fx());
+ rightSensorIntrinsics.setFy(sensorIntrinsics.right_cam().fy());
+ rightSensorIntrinsics.setCx(sensorIntrinsics.right_cam().cx());
+ rightSensorIntrinsics.setCy(sensorIntrinsics.right_cam().cy());
+ sensorIntrinsics.close();
+
+ // Get center to camera distance
+ SL_CameraInformation cameraInformation = sl_get_camera_information(cameraID, 0, 0);
+ sensorCenterToCameraDistanceY = 0.5f * cameraInformation.camera_configuration().calibration_parameters().translation().y();
+ cameraInformation.close();
+
+ // Create image retrieval pointers
+ slMatPointers[LEFT_COLOR_IMAGE_KEY] = new Pointer(sl_mat_create_new(imageWidth, imageHeight, SL_MAT_TYPE_U8_C4, SL_MEM_GPU));
+ slMatPointers[RIGHT_COLOR_IMAGE_KEY] = new Pointer(sl_mat_create_new(imageWidth, imageHeight, SL_MAT_TYPE_U8_C4, SL_MEM_GPU));
+ slMatPointers[DEPTH_IMAGE_KEY] = new Pointer(sl_mat_create_new(imageWidth, imageHeight, SL_MAT_TYPE_U16_C1, SL_MEM_GPU));
+ }
+ catch (ZEDException exception)
+ {
+ LogTools.error(exception);
+ return false;
+ }
+
+ lastGrabFailed = false;
+ return true;
+ }
+
+ protected void setInitParameters(SL_InitParameters parametersToSet)
+ {
+ parametersToSet.camera_fps(CAMERA_FPS);
+ parametersToSet.resolution(SL_RESOLUTION_HD720);
+ parametersToSet.input_type(slInputType);
+ parametersToSet.camera_device_id(cameraID);
+ parametersToSet.camera_image_flip(SL_FLIP_MODE_OFF);
+ parametersToSet.camera_disable_self_calib(false);
+ parametersToSet.enable_image_enhancement(true);
+ parametersToSet.depth_mode(SL_DEPTH_MODE_ULTRA);
+ parametersToSet.depth_stabilization(1);
+ parametersToSet.depth_maximum_distance(zedModel.getMaximumDepthDistance());
+ parametersToSet.depth_minimum_distance(zedModel.getMinimumDepthDistance());
+ parametersToSet.coordinate_unit(SL_UNIT_METER);
+ parametersToSet.coordinate_system(SL_COORDINATE_SYSTEM_RIGHT_HANDED_Z_UP_X_FWD);
+ parametersToSet.sdk_gpu_id(-1); // Will find and use the best available GPU
+ parametersToSet.sdk_verbose(0); // false
+ parametersToSet.sensors_required(true);
+ parametersToSet.enable_right_side_measure(false);
+ parametersToSet.open_timeout_sec(5.0f);
+ parametersToSet.async_grab_camera_recovery(false);
+ }
+
+ protected int openCamera()
+ {
+ return sl_open_camera(cameraID, zedInitParameters, 0, "", "", 0, "", "", "");
+ }
+
+ @Override
+ public boolean isSensorRunning()
+ {
+ return sl_is_opened(cameraID) && !lastGrabFailed;
+ }
+
+ @Override
+ protected boolean grab()
+ {
+ // Update the sensor pose
+ ReferenceFrame sensorFrame = sensorFrameSupplier.get();
+
+ leftSensorPose.setToZero(sensorFrame);
+ leftSensorPose.getPosition().subY(sensorCenterToCameraDistanceY);
+ leftSensorPose.changeFrame(ReferenceFrame.getWorldFrame());
+
+ rightSensorPose.setToZero(sensorFrame);
+ rightSensorPose.getPosition().addY(sensorCenterToCameraDistanceY);
+ rightSensorPose.changeFrame(ReferenceFrame.getWorldFrame());
+
+ int returnCode;
+ try
+ {
+ // Grab images now
+ returnCode = sl_grab(cameraID, zedRuntimeParameters);
+ throwOnError(returnCode);
+ lastGrabTime = Instant.now();
+ ++grabSequenceNumber;
+
+ // Update tracked position if tracking enabled
+ if (positionalTrackingEnabled)
+ {
+ sl_get_position(cameraID, sensorRotation, sensorTranslation, SL_REFERENCE_FRAME_WORLD);
+
+ Quaternion euclidRotation = new Quaternion(sensorRotation.x(), sensorRotation.y(), sensorRotation.z(), sensorRotation.w());
+ Vector3D euclidTranslation = new Vector3D(sensorTranslation.x(), sensorTranslation.y(), sensorTranslation.z());
+ if (!euclidRotation.containsNaN() && !euclidTranslation.containsNaN())
+ trackedSensorFrame.update(transformToWorld -> transformToWorld.set(euclidRotation, euclidTranslation));
+ }
+
+ // Retrieve the grabbed depth image
+ Pointer depthImagePointer = slMatPointers[DEPTH_IMAGE_KEY];
+ returnCode = sl_retrieve_measure(cameraID, depthImagePointer, SL_MEASURE_DEPTH_U16_MM, SL_MEM_GPU, imageWidth, imageHeight);
+ throwOnError(returnCode);
+
+ // Retrieve the grabbed left color image
+ Pointer leftColorImagePointer = slMatPointers[LEFT_COLOR_IMAGE_KEY];
+ returnCode = sl_retrieve_image(cameraID, leftColorImagePointer, SL_VIEW_LEFT, SL_MEM_GPU, imageWidth, imageHeight);
+ throwOnError(returnCode);
+
+ // Retrieve the grabbed right color image
+ Pointer rightColorImagePointer = slMatPointers[RIGHT_COLOR_IMAGE_KEY];
+ returnCode = sl_retrieve_image(cameraID, rightColorImagePointer, SL_VIEW_RIGHT, SL_MEM_GPU, imageWidth, imageHeight);
+ throwOnError(returnCode);
+
+ synchronized (grabbedImages)
+ { // Create RawImages from the grabbed retrieved slMats
+ grabbedImages[LEFT_COLOR_IMAGE_KEY] = slMatToRawImage(leftColorImagePointer, PixelFormat.BGRA8, leftSensorIntrinsics, leftSensorPose);
+ grabbedImages[RIGHT_COLOR_IMAGE_KEY] = slMatToRawImage(rightColorImagePointer, PixelFormat.BGRA8, rightSensorIntrinsics, rightSensorPose);
+ grabbedImages[DEPTH_IMAGE_KEY] = slMatToRawImage(depthImagePointer, PixelFormat.GRAY16, leftSensorIntrinsics, leftSensorPose);
+ }
+ }
+ catch (ZEDException exception)
+ {
+ LogTools.error(exception);
+ lastGrabFailed = true;
+ return false;
+ }
+
+ lastGrabFailed = false;
+ return true;
+ }
+
+ private RawImage slMatToRawImage(Pointer slMatPointer, PixelFormat imagePixelFormat, CameraIntrinsics cameraIntrinsics, FramePose3D sensorPose)
+ {
+ GpuMat imageGpuMat = new GpuMat(imageHeight,
+ imageWidth,
+ imagePixelFormat.toOpenCVType(),
+ sl_mat_get_ptr(slMatPointer, SL_MEM_GPU),
+ sl_mat_get_step_bytes(slMatPointer, SL_MEM_GPU));
+ return new RawImage(null,
+ imageGpuMat,
+ imagePixelFormat,
+ cameraIntrinsics,
+ CameraModel.PINHOLE,
+ new FramePose3D(sensorPose),
+ lastGrabTime,
+ grabSequenceNumber,
+ MILLIMETER_TO_METERS);
+ }
+
+ @Override
+ public RawImage getImage(int imageKey)
+ {
+ synchronized (grabbedImages)
+ {
+ return grabbedImages[imageKey].get();
+ }
+ }
+
+ public ReferenceFrame getTrackedSensorFrame()
+ {
+ return trackedSensorFrame.getReferenceFrame();
+ }
+
+ public int getCameraID()
+ {
+ return cameraID;
+ }
+
+ public void enablePositionalTracking(boolean enable)
+ {
+ positionalTrackingEnabled = enable;
+ }
+
+ @Override
+ public void close()
+ {
+ System.out.println("Closing " + getClass().getSimpleName());
+ super.close();
+
+ for (Pointer slMat : slMatPointers)
+ {
+ if (slMat != null && !slMat.isNull())
+ {
+ sl_mat_free(slMat, SL_MEM_GPU);
+ slMat.close();
+ }
+ }
+
+ sl_close_camera(cameraID);
+
+ System.out.println("Closed " + getClass().getSimpleName());
+ }
+
+ private void throwOnError(int errorCode) throws ZEDException
+ {
+ if (errorCode != SL_ERROR_CODE_SUCCESS)
+ throw new ZEDException(errorCode);
+ }
+
+ private class ZEDException extends Exception
+ {
+ private final int zedErrorCode;
+
+ public ZEDException(int zedErrorCode)
+ {
+ this.zedErrorCode = zedErrorCode;
+ }
+
+ @Override
+ public String getMessage()
+ {
+ return "ZED Error (%d): %s".formatted(zedErrorCode, getZEDErrorName(zedErrorCode));
+ }
+ }
+
+ private String getZEDErrorName(int errorCode)
+ {
+ return switch (errorCode)
+ {
+ case SL_ERROR_CODE_CORRUPTED_FRAME -> "SL_ERROR_CODE_CORRUPTED_FRAME";
+ case SL_ERROR_CODE_CAMERA_REBOOTING -> "SL_ERROR_CODE_CAMERA_REBOOTING";
+ case SL_ERROR_CODE_SUCCESS -> "SL_ERROR_CODE_SUCCESS";
+ case SL_ERROR_CODE_FAILURE -> "SL_ERROR_CODE_FAILURE";
+ case SL_ERROR_CODE_NO_GPU_COMPATIBLE -> "SL_ERROR_CODE_NO_GPU_COMPATIBLE";
+ case SL_ERROR_CODE_NOT_ENOUGH_GPU_MEMORY -> "SL_ERROR_CODE_NOT_ENOUGH_GPU_MEMORY";
+ case SL_ERROR_CODE_CAMERA_NOT_DETECTED -> "SL_ERROR_CODE_CAMERA_NOT_DETECTED";
+ case SL_ERROR_CODE_SENSORS_NOT_INITIALIZED -> "SL_ERROR_CODE_SENSORS_NOT_INITIALIZED";
+ case SL_ERROR_CODE_SENSORS_NOT_AVAILABLE -> "SL_ERROR_CODE_SENSORS_NOT_AVAILABLE";
+ case SL_ERROR_CODE_INVALID_RESOLUTION -> "SL_ERROR_CODE_INVALID_RESOLUTION";
+ case SL_ERROR_CODE_LOW_USB_BANDWIDTH -> "SL_ERROR_CODE_LOW_USB_BANDWIDTH";
+ case SL_ERROR_CODE_CALIBRATION_FILE_NOT_AVAILABLE -> "SL_ERROR_CODE_CALIBRATION_FILE_NOT_AVAILABLE";
+ case SL_ERROR_CODE_INVALID_CALIBRATION_FILE -> "SL_ERROR_CODE_INVALID_CALIBRATION_FILE";
+ case SL_ERROR_CODE_INVALID_SVO_FILE -> "SL_ERROR_CODE_INVALID_SVO_FILE";
+ case SL_ERROR_CODE_SVO_RECORDING_ERROR -> "SL_ERROR_CODE_SVO_RECORDING_ERROR";
+ case SL_ERROR_CODE_SVO_UNSUPPORTED_COMPRESSION -> "SL_ERROR_CODE_SVO_UNSUPPORTED_COMPRESSION";
+ case SL_ERROR_CODE_END_OF_SVOFILE_REACHED -> "SL_ERROR_CODE_END_OF_SVOFILE_REACHED";
+ case SL_ERROR_CODE_INVALID_COORDINATE_SYSTEM -> "SL_ERROR_CODE_INVALID_COORDINATE_SYSTEM";
+ case SL_ERROR_CODE_INVALID_FIRMWARE -> "SL_ERROR_CODE_INVALID_FIRMWARE";
+ case SL_ERROR_CODE_INVALID_FUNCTION_PARAMETERS -> "SL_ERROR_CODE_INVALID_FUNCTION_PARAMETERS";
+ case SL_ERROR_CODE_CUDA_ERROR -> "SL_ERROR_CODE_CUDA_ERROR";
+ case SL_ERROR_CODE_CAMERA_NOT_INITIALIZED -> "SL_ERROR_CODE_CAMERA_NOT_INITIALIZED";
+ case SL_ERROR_CODE_NVIDIA_DRIVER_OUT_OF_DATE -> "SL_ERROR_CODE_NVIDIA_DRIVER_OUT_OF_DATE";
+ case SL_ERROR_CODE_INVALID_FUNCTION_CALL -> "SL_ERROR_CODE_INVALID_FUNCTION_CALL";
+ case SL_ERROR_CODE_CORRUPTED_SDK_INSTALLATION -> "SL_ERROR_CODE_CORRUPTED_SDK_INSTALLATION";
+ case SL_ERROR_CODE_INCOMPATIBLE_SDK_VERSION -> "SL_ERROR_CODE_INCOMPATIBLE_SDK_VERSION";
+ case SL_ERROR_CODE_INVALID_AREA_FILE -> "SL_ERROR_CODE_INVALID_AREA_FILE";
+ case SL_ERROR_CODE_INCOMPATIBLE_AREA_FILE -> "SL_ERROR_CODE_INCOMPATIBLE_AREA_FILE";
+ case SL_ERROR_CODE_CAMERA_FAILED_TO_SETUP -> "SL_ERROR_CODE_CAMERA_FAILED_TO_SETUP";
+ case SL_ERROR_CODE_CAMERA_DETECTION_ISSUE -> "SL_ERROR_CODE_CAMERA_DETECTION_ISSUE";
+ case SL_ERROR_CODE_CANNOT_START_CAMERA_STREAM -> "SL_ERROR_CODE_CANNOT_START_CAMERA_STREAM";
+ case SL_ERROR_CODE_NO_GPU_DETECTED -> "SL_ERROR_CODE_NO_GPU_DETECTED";
+ case SL_ERROR_CODE_PLANE_NOT_FOUND -> "SL_ERROR_CODE_PLANE_NOT_FOUND";
+ case SL_ERROR_CODE_MODULE_NOT_COMPATIBLE_WITH_CAMERA -> "SL_ERROR_CODE_MODULE_NOT_COMPATIBLE_WITH_CAMERA";
+ case SL_ERROR_CODE_MOTION_SENSORS_REQUIRED -> "SL_ERROR_CODE_MOTION_SENSORS_REQUIRED";
+ case SL_ERROR_CODE_MODULE_NOT_COMPATIBLE_WITH_CUDA_VERSION -> "SL_ERROR_CODE_MODULE_NOT_COMPATIBLE_WITH_CUDA_VERSION";
+ case SL_ERROR_CODE_SENSORS_DATA_REQUIRED -> "SL_ERROR_CODE_SENSORS_DATA_REQUIRED";
+ default -> "UNKNOWN";
+ };
+ }
+}
diff --git a/ihmc-perception/src/main/java/us/ihmc/sensors/ZEDSVOPlaybackSensor.java b/ihmc-perception/src/main/java/us/ihmc/sensors/ZEDSVOPlaybackSensor.java
new file mode 100644
index 00000000000..ee51ecfaafe
--- /dev/null
+++ b/ihmc-perception/src/main/java/us/ihmc/sensors/ZEDSVOPlaybackSensor.java
@@ -0,0 +1,96 @@
+package us.ihmc.sensors;
+
+import perception_msgs.msg.dds.ZEDSVOCurrentFileMessage;
+import us.ihmc.commons.thread.RepeatingTaskThread;
+import us.ihmc.communication.PerceptionAPI;
+import us.ihmc.communication.ros2.ROS2PublishSubscribeAPI;
+import us.ihmc.zed.SL_InitParameters;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static us.ihmc.zed.global.zed.*;
+
+public class ZEDSVOPlaybackSensor extends ZEDImageSensor
+{
+ private final String svoFileName;
+ private final ZEDSVOCurrentFileMessage svoStatusMessage = new ZEDSVOCurrentFileMessage();
+ private final RepeatingTaskThread publishInfoThread;
+
+ public ZEDSVOPlaybackSensor(ROS2PublishSubscribeAPI ros2, int cameraID, ZEDModelData zedModel, String svoFileName)
+ {
+ super(cameraID, zedModel, SL_INPUT_TYPE_SVO);
+ this.svoFileName = svoFileName;
+
+ if (!Files.exists(Path.of(svoFileName)))
+ throw new RuntimeException("SVO file does not exist");
+
+ // Subscription to set position message
+ ros2.subscribeViaCallback(PerceptionAPI.ZED_SVO_SET_POSITION, position ->
+ {
+ setCurrentPosition((int) position.getData());
+ if (getGrabThread().getScheduled() == 0)
+ getGrabThread().setScheduled(1);
+ });
+
+ // Subscribe for pause message
+ ros2.subscribeViaCallback(PerceptionAPI.ZED_SVO_PAUSE, () -> getGrabThread().stopRepeating());
+
+ // Subscribe to play message
+ ros2.subscribeViaCallback(PerceptionAPI.ZED_SVO_PLAY, () -> getGrabThread().startRepeating());
+
+ publishInfoThread = new RepeatingTaskThread("PublishSVOInfoThread", () ->
+ {
+ svoStatusMessage.setCurrentFileName(svoFileName);
+ svoStatusMessage.setRecordMode((byte) 1); // playback
+ svoStatusMessage.setCurrentPosition(getCurrentPosition());
+ svoStatusMessage.setLength(getLength());
+
+ ros2.publish(PerceptionAPI.ZED_SVO_CURRENT_FILE, svoStatusMessage);
+ }).setFrequencyLimit(ZEDImageSensor.CAMERA_FPS);
+ publishInfoThread.start();
+ }
+
+ public void useTrackedPose(boolean useTrackedPose)
+ {
+ enablePositionalTracking(useTrackedPose);
+ if (useTrackedPose)
+ setSensorFrameSupplier(this::getTrackedSensorFrame);
+ }
+
+ @Override
+ protected void setInitParameters(SL_InitParameters parametersToSet)
+ {
+ super.setInitParameters(parametersToSet);
+ parametersToSet.svo_real_time_mode(true);
+ }
+
+ @Override
+ protected int openCamera()
+ {
+ return sl_open_camera(getCameraID(), zedInitParameters, 0, svoFileName, "", 0, "", "", "");
+ }
+
+ @Override
+ public void close()
+ {
+ publishInfoThread.blockingKill();
+
+ super.close();
+ }
+
+ public int getLength()
+ {
+ return sl_get_svo_number_of_frames(getCameraID());
+ }
+
+ public int getCurrentPosition()
+ {
+ return sl_get_svo_position(getCameraID());
+ }
+
+ public void setCurrentPosition(int position)
+ {
+ sl_set_svo_position(getCameraID(), position);
+ }
+}
From a45172c54e095da40a6cdf9f901aec72423f47eb Mon Sep 17 00:00:00 2001
From: Duncan Calvert
Date: Wed, 18 Dec 2024 13:21:15 -0600
Subject: [PATCH 13/16] Implement ROS 2 peer clock offset estimator (#532)
---
ihmc-communication/build.gradle.kts | 1 +
.../communication/packets/MessageTools.java | 23 +++
.../sync/ROS2PeerClockOffsetEstimator.java | 153 +++++++++++++++
.../ROS2PeerClockOffsetEstimatorPeer.java | 61 ++++++
.../ros2/sync/RDXROS2PeerClockTest.java | 117 +++++++++++
.../ROS2PeerClockOffsetEstimatorTest.java | 56 ++++++
.../RDXROS2PeerClockTest/ImGuiPanels.json | 7 +
.../RDXROS2PeerClockTest/ImGuiSettings.ini | 24 +++
.../ihmc_common_msgs/msg/GuidMessage_.idl | 30 +++
.../PeerClockOffsetEstimatorPingMessage_.idl | 45 +++++
.../ihmc_common_msgs/msg/dds/GuidMessage.java | 139 +++++++++++++
.../msg/dds/GuidMessagePubSubType.java | 165 ++++++++++++++++
.../PeerClockOffsetEstimatorPingMessage.java | 177 +++++++++++++++++
...kOffsetEstimatorPingMessagePubSubType.java | 184 ++++++++++++++++++
.../ihmc_common_msgs/msg/GuidMessage.msg | 7 +
.../PeerClockOffsetEstimatorPingMessage.msg | 17 ++
.../ros1/ihmc_common_msgs/msg/GuidMessage.msg | 9 +
.../PeerClockOffsetEstimatorPingMessage.msg | 19 ++
18 files changed, 1234 insertions(+)
create mode 100644 ihmc-communication/src/main/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimator.java
create mode 100644 ihmc-communication/src/main/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimatorPeer.java
create mode 100644 ihmc-communication/src/test/java/us/ihmc/communication/ros2/sync/RDXROS2PeerClockTest.java
create mode 100644 ihmc-communication/src/test/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimatorTest.java
create mode 100644 ihmc-communication/src/test/resources/configurations/RDXROS2PeerClockTest/ImGuiPanels.json
create mode 100644 ihmc-communication/src/test/resources/configurations/RDXROS2PeerClockTest/ImGuiSettings.ini
create mode 100644 ihmc-interfaces/src/main/generated-idl/ihmc_common_msgs/msg/GuidMessage_.idl
create mode 100644 ihmc-interfaces/src/main/generated-idl/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage_.idl
create mode 100644 ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/GuidMessage.java
create mode 100644 ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/GuidMessagePubSubType.java
create mode 100644 ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/PeerClockOffsetEstimatorPingMessage.java
create mode 100644 ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/PeerClockOffsetEstimatorPingMessagePubSubType.java
create mode 100644 ihmc-interfaces/src/main/messages/ihmc_interfaces/ihmc_common_msgs/msg/GuidMessage.msg
create mode 100644 ihmc-interfaces/src/main/messages/ihmc_interfaces/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage.msg
create mode 100644 ihmc-interfaces/src/main/messages/ros1/ihmc_common_msgs/msg/GuidMessage.msg
create mode 100644 ihmc-interfaces/src/main/messages/ros1/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage.msg
diff --git a/ihmc-communication/build.gradle.kts b/ihmc-communication/build.gradle.kts
index d4f9044bef7..96cbbf1e895 100644
--- a/ihmc-communication/build.gradle.kts
+++ b/ihmc-communication/build.gradle.kts
@@ -24,5 +24,6 @@ mainDependencies {
testDependencies {
api("us.ihmc:ihmc-robotics-toolkit-test:source")
+ api("us.ihmc:ihmc-graphics-libgdx:source")
api("us.ihmc:ros2-library-test:1.1.4")
}
diff --git a/ihmc-communication/src/main/java/us/ihmc/communication/packets/MessageTools.java b/ihmc-communication/src/main/java/us/ihmc/communication/packets/MessageTools.java
index d09aa202bc9..7858649b003 100644
--- a/ihmc-communication/src/main/java/us/ihmc/communication/packets/MessageTools.java
+++ b/ihmc-communication/src/main/java/us/ihmc/communication/packets/MessageTools.java
@@ -17,6 +17,7 @@
import ihmc_common_msgs.msg.dds.ConvexPolytope3DMessage;
import ihmc_common_msgs.msg.dds.Cylinder3DMessage;
import ihmc_common_msgs.msg.dds.Ellipsoid3DMessage;
+import ihmc_common_msgs.msg.dds.GuidMessage;
import ihmc_common_msgs.msg.dds.InstantMessage;
import ihmc_common_msgs.msg.dds.PoseListMessage;
import ihmc_common_msgs.msg.dds.Ramp3DMessage;
@@ -82,6 +83,9 @@
import us.ihmc.mecano.multiBodySystem.interfaces.RigidBodyReadOnly;
import us.ihmc.mecano.spatial.interfaces.TwistReadOnly;
import us.ihmc.pubsub.TopicDataType;
+import us.ihmc.pubsub.common.Guid;
+import us.ihmc.pubsub.common.Guid.Entity;
+import us.ihmc.pubsub.common.Guid.GuidPrefix;
import us.ihmc.pubsub.common.SerializedPayload;
import us.ihmc.robotics.lidar.LidarScanParameters;
import us.ihmc.robotics.math.QuaternionCalculus;
@@ -1422,6 +1426,25 @@ public static UUID toUUID(UUIDMessage uuidMessage)
return new UUID(uuidMessage.getMostSignificantBits(), uuidMessage.getLeastSignificantBits());
}
+ public static void toMessage(Guid guid, GuidMessage guidMessage)
+ {
+ System.arraycopy(guid.getGuidPrefix().getValue(), 0, guidMessage.getPrefix(), 0, GuidPrefix.size);
+ System.arraycopy(guid.getEntity().getValue(), 0, guidMessage.getEntity(), 0, Entity.size);
+ }
+
+ public static void fromMessage(GuidMessage guidMessage, Guid guid)
+ {
+ System.arraycopy(guidMessage.getPrefix(), 0, guid.getGuidPrefix().getValue(), 0, GuidPrefix.size);
+ System.arraycopy(guidMessage.getEntity(), 0, guid.getEntity().getValue(), 0, Entity.size);
+ }
+
+ public static Guid toGuid(GuidMessage guidMessage)
+ {
+ Guid guid = new Guid();
+ fromMessage(guidMessage, guid);
+ return guid;
+ }
+
public static double calculateDelay(ImageMessage imageMessage)
{
return TimeTools.calculateDelay(imageMessage.getAcquisitionTime().getSecondsSinceEpoch(), imageMessage.getAcquisitionTime().getAdditionalNanos());
diff --git a/ihmc-communication/src/main/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimator.java b/ihmc-communication/src/main/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimator.java
new file mode 100644
index 00000000000..bb9c76a6ceb
--- /dev/null
+++ b/ihmc-communication/src/main/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimator.java
@@ -0,0 +1,153 @@
+package us.ihmc.communication.ros2.sync;
+
+import ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage;
+import us.ihmc.commons.exception.DefaultExceptionHandler;
+import us.ihmc.commons.exception.ExceptionTools;
+import us.ihmc.commons.thread.RepeatingTaskThread;
+import us.ihmc.commons.thread.ThreadTools;
+import us.ihmc.communication.ROS2Tools;
+import us.ihmc.communication.packets.MessageTools;
+import us.ihmc.pubsub.common.Guid;
+import us.ihmc.pubsub.common.SampleInfo;
+import us.ihmc.ros2.ROS2Node;
+import us.ihmc.ros2.ROS2Publisher;
+import us.ihmc.ros2.ROS2Subscription;
+import us.ihmc.ros2.ROS2Topic;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Sends out pings to other instances of this class on the network to estimate
+ * the clock offsets on those computers. This can be used to synchronize data
+ * modifications. We account for and assume a symmetric network delay.
+ */
+public class ROS2PeerClockOffsetEstimator
+{
+ private static final ROS2Topic TOPIC = ROS2Tools.IHMC_ROOT.withModule("peer_clock_offset_estimator")
+ .withType(PeerClockOffsetEstimatorPingMessage.class);
+
+ private final HashMap peerMap = new HashMap<>();
+ private final List peerList = new ArrayList<>();
+ private int nextPeerToPing = 0;
+ private final ROS2Publisher publisher;
+ private final Guid ourGuid;
+ private final RepeatingTaskThread requestThread = new RepeatingTaskThread(getClass().getSimpleName(),
+ this::runRequestTask,
+ DefaultExceptionHandler.MESSAGE_AND_STACKTRACE);
+ private final ROS2Subscription subscription;
+ private final ExecutorService cachedThreadPool
+ = Executors.newCachedThreadPool(ThreadTools.createNamedThreadFactory(getClass().getSimpleName() + "PublishReply", true));
+ private final PeerClockOffsetEstimatorPingMessage requestMessage = new PeerClockOffsetEstimatorPingMessage();
+ private final PeerClockOffsetEstimatorPingMessage receivedMessage = new PeerClockOffsetEstimatorPingMessage();
+ private final SampleInfo sampleInfo = new SampleInfo();
+ private final Guid receivedRequestTarget = new Guid();
+ private final Guid receivedReplyTarget = new Guid();
+
+ public ROS2PeerClockOffsetEstimator(ROS2Node ros2Node)
+ {
+ publisher = ros2Node.createPublisher(TOPIC);
+ ourGuid = publisher.getPublisher().getGuid();
+
+ subscription = ros2Node.createSubscription(TOPIC, subscriber ->
+ {
+ subscriber.takeNextData(receivedMessage, sampleInfo);
+
+ MessageTools.fromMessage(receivedMessage.getRequestTarget(), receivedRequestTarget);
+ MessageTools.fromMessage(receivedMessage.getReplyTarget(), receivedReplyTarget);
+
+ if (receivedMessage.getIsRequest() && receivedRequestTarget.equals(ourGuid)) // Reply
+ {
+ PeerClockOffsetEstimatorPingMessage replyMessage = new PeerClockOffsetEstimatorPingMessage();
+ replyMessage.set(receivedMessage);
+ replyMessage.setIsRequest(false);
+ MessageTools.toMessage(Instant.now(), replyMessage.getReplySendTime());
+
+ cachedThreadPool.submit(() -> ExceptionTools.handle(() ->
+ {
+ synchronized (publisher)
+ {
+ publisher.publish(replyMessage);
+ }
+ }, DefaultExceptionHandler.MESSAGE_AND_STACKTRACE));
+ }
+ else if (!receivedMessage.getIsRequest() && receivedReplyTarget.equals(ourGuid)) // Update clock offset estimate
+ {
+ ROS2PeerClockOffsetEstimatorPeer peer = peerMap.get(receivedRequestTarget);
+ if (peer != null)
+ {
+ peer.update(MessageTools.toInstant(receivedMessage.getRequestSendTime()),
+ Instant.now(),
+ MessageTools.toInstant(receivedMessage.getReplySendTime()));
+ }
+ }
+ }, (subscriber, info) ->
+ {
+ Guid guid = info.getGuid();
+ if (!guid.equals(publisher.getPublisher().getGuid())) // Exclude our publisher
+ {
+ switch (info.getStatus())
+ {
+ case MATCHED_MATCHING ->
+ {
+ if (!peerMap.containsKey(guid))
+ {
+ Guid guidCopy = new Guid();
+ guidCopy.set(guid);
+
+ ROS2PeerClockOffsetEstimatorPeer peer = new ROS2PeerClockOffsetEstimatorPeer(guidCopy);
+ peerMap.put(guidCopy, peer);
+ peerList.add(peer);
+ }
+ }
+ case REMOVED_MATCHING ->
+ {
+ peerMap.remove(guid);
+ peerList.removeIf(peer -> peer.getGuid().equals(guid));
+ }
+ }
+ }
+ });
+
+ requestThread.setFrequencyLimit(5.0);
+ requestThread.startRepeating();
+ }
+
+ private void runRequestTask()
+ {
+ if (!peerList.isEmpty())
+ {
+ if (nextPeerToPing >= peerList.size())
+ nextPeerToPing = 0;
+
+ ROS2PeerClockOffsetEstimatorPeer peer = peerList.get(nextPeerToPing);
+ requestMessage.setIsRequest(true);
+ MessageTools.toMessage(peer.getGuid(), requestMessage.getRequestTarget());
+ MessageTools.toMessage(ourGuid, requestMessage.getReplyTarget());
+ MessageTools.toMessage(Instant.now(), requestMessage.getRequestSendTime());
+ synchronized (publisher)
+ {
+ publisher.publish(requestMessage);
+ }
+
+ ++nextPeerToPing;
+ }
+ }
+
+ public void destroy()
+ {
+ subscription.remove();
+ publisher.remove();
+ cachedThreadPool.shutdownNow();
+ requestThread.kill();
+ }
+
+ public List getPeerList()
+ {
+ return peerList;
+ }
+}
diff --git a/ihmc-communication/src/main/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimatorPeer.java b/ihmc-communication/src/main/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimatorPeer.java
new file mode 100644
index 00000000000..0bdbfa3774f
--- /dev/null
+++ b/ihmc-communication/src/main/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimatorPeer.java
@@ -0,0 +1,61 @@
+package us.ihmc.communication.ros2.sync;
+
+import us.ihmc.pubsub.common.Guid;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+
+/**
+ * Keeps track of the clock offset of a peer.
+ *
+ * TODO: Add an alpha filter since the network latency and compute
+ * is noisy, but the clock rates are smooth.
+ */
+public class ROS2PeerClockOffsetEstimatorPeer
+{
+ private final Guid guid;
+ private Duration peerClockOffset;
+ private Instant replyReceiveTime;
+
+ public ROS2PeerClockOffsetEstimatorPeer(Guid guid)
+ {
+ this.guid = guid;
+ }
+
+ public void update(Instant requestSendTime, Instant replyReceiveTime, Instant peerClockTime)
+ {
+ this.replyReceiveTime = replyReceiveTime;
+
+ Duration roundTripTime = Duration.between(requestSendTime, replyReceiveTime);
+ Duration halfRoundTripTime = roundTripTime.dividedBy(2);
+ Instant peerNow = peerClockTime.plus(halfRoundTripTime);
+ peerClockOffset = Duration.between(replyReceiveTime, peerNow);
+ }
+
+ public Instant getPeerTimeInLocalFrame(Instant peerTime)
+ {
+ return peerTime.minus(peerClockOffset);
+ }
+
+ public Instant getPeerTimeInPeerFrame(Instant ourTime)
+ {
+ return ourTime.plus(peerClockOffset);
+ }
+
+ public boolean isAlive(Instant ourTime)
+ {
+ return replyReceiveTime != null && replyReceiveTime.isAfter(ourTime.minus(1, ChronoUnit.SECONDS));
+ }
+
+ public Duration getPeerClockOffset()
+ {
+ return peerClockOffset;
+ }
+
+ public Guid getGuid()
+ {
+ return guid;
+ }
+}
diff --git a/ihmc-communication/src/test/java/us/ihmc/communication/ros2/sync/RDXROS2PeerClockTest.java b/ihmc-communication/src/test/java/us/ihmc/communication/ros2/sync/RDXROS2PeerClockTest.java
new file mode 100644
index 00000000000..03db470ba5e
--- /dev/null
+++ b/ihmc-communication/src/test/java/us/ihmc/communication/ros2/sync/RDXROS2PeerClockTest.java
@@ -0,0 +1,117 @@
+package us.ihmc.communication.ros2.sync;
+
+import imgui.ImGui;
+import us.ihmc.commons.thread.Throttler;
+import us.ihmc.pubsub.common.Guid;
+import us.ihmc.rdx.Lwjgl3ApplicationAdapter;
+import us.ihmc.rdx.imgui.ImGuiTools;
+import us.ihmc.rdx.imgui.ImGuiUniqueLabelMap;
+import us.ihmc.rdx.ui.RDXBaseUI;
+import us.ihmc.ros2.ROS2Node;
+import us.ihmc.ros2.ROS2NodeBuilder;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RDXROS2PeerClockTest
+{
+ public RDXROS2PeerClockTest()
+ {
+ RDXBaseUI baseUI = new RDXBaseUI();
+ baseUI.launchRDXApplication(new Lwjgl3ApplicationAdapter()
+ {
+ private ROS2Node ros2Node;
+ private ROS2PeerClockOffsetEstimator clockEstimator;
+ private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss:SSS");
+ private final Map plots = new HashMap<>();
+ private final ImGuiUniqueLabelMap labels = new ImGuiUniqueLabelMap(getClass());
+ private final double history = 5.0;
+ private final int samples = 1000;
+ private final Throttler plotThottler = new Throttler().setPeriod(history / samples);
+
+ @Override
+ public void create()
+ {
+ baseUI.create();
+ baseUI.getImGuiPanelManager().addPanel("Main", this::renderImGuiWidgets);
+
+ ros2Node = new ROS2NodeBuilder().build("peer_clock_test");
+ clockEstimator = new ROS2PeerClockOffsetEstimator(ros2Node);
+ }
+
+ @Override
+ public void render()
+ {
+ baseUI.renderBeforeOnScreenUI();
+ baseUI.renderEnd();
+ }
+
+ @Override
+ public void dispose()
+ {
+ clockEstimator.destroy();
+ ros2Node.destroy();
+ baseUI.dispose();
+ }
+
+ private void renderImGuiWidgets()
+ {
+ Instant now = Instant.now();
+
+ ImGui.text("Current time:");
+ ImGui.pushFont(ImGuiTools.getBigFont());
+ ImGui.text(formatter.format(now.atZone(ZoneId.systemDefault())));
+ ImGui.popFont();
+
+ boolean shiftPlots = plotThottler.run();
+
+ for (ROS2PeerClockOffsetEstimatorPeer peer : clockEstimator.getPeerList())
+ {
+ if (peer.isAlive(now))
+ {
+ ImGui.separator();
+ ImGui.text("Peer %s time (peer frame):".formatted(peer.getGuid()));
+ ImGui.pushFont(ImGuiTools.getBigFont());
+ ImGui.text(formatter.format(peer.getPeerTimeInPeerFrame(now).atZone(ZoneId.systemDefault())));
+ ImGui.popFont();
+ long offsetInMillis = peer.getPeerClockOffset().toMillis();
+ ImGui.text("Offset (ms):");
+
+ float[] plot = plots.get(peer.getGuid());
+ if (plot == null)
+ {
+ plot = new float[samples];
+ plots.put(peer.getGuid(), plot);
+ }
+
+ if (shiftPlots)
+ for (int i = 0; i < plot.length - 1; i++)
+ plot[i] = plot[i + 1];
+
+ plot[plot.length - 1] = offsetInMillis;
+
+ ImGui.pushFont(ImGuiTools.getBigFont());
+ ImGui.plotLines(labels.get("Offset (ms)", peer.getGuid().hashCode()),
+ plot,
+ samples,
+ 0,
+ "%d ms".formatted(offsetInMillis),
+ -30.0f,
+ 30.0f,
+ ImGui.getColumnWidth(),
+ 70.0f);
+ ImGui.popFont();
+ }
+ }
+ }
+ });
+ }
+
+ public static void main(String[] args)
+ {
+ new RDXROS2PeerClockTest();
+ }
+}
diff --git a/ihmc-communication/src/test/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimatorTest.java b/ihmc-communication/src/test/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimatorTest.java
new file mode 100644
index 00000000000..6c8f49b421d
--- /dev/null
+++ b/ihmc-communication/src/test/java/us/ihmc/communication/ros2/sync/ROS2PeerClockOffsetEstimatorTest.java
@@ -0,0 +1,56 @@
+package us.ihmc.communication.ros2.sync;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import us.ihmc.commons.thread.ThreadTools;
+import us.ihmc.log.LogTools;
+import us.ihmc.robotics.time.TimeTools;
+import us.ihmc.ros2.ROS2Node;
+import us.ihmc.ros2.ROS2NodeBuilder;
+import us.ihmc.ros2.ROS2NodeBuilder.SpecialTransportMode;
+
+import java.time.Duration;
+import java.time.Instant;
+
+public class ROS2PeerClockOffsetEstimatorTest
+{
+ @Test
+ public void test()
+ {
+ ROS2Node ros2Node0 = new ROS2NodeBuilder().specialTransportMode(SpecialTransportMode.INTRAPROCESS_ONLY).build("peer_clock_test0");
+ ROS2Node ros2Node1 = new ROS2NodeBuilder().specialTransportMode(SpecialTransportMode.INTRAPROCESS_ONLY).build("peer_clock_test1");
+
+ ROS2PeerClockOffsetEstimator[] clocks = new ROS2PeerClockOffsetEstimator[3];
+ clocks[0] = new ROS2PeerClockOffsetEstimator(ros2Node0);
+ clocks[1] = new ROS2PeerClockOffsetEstimator(ros2Node0);
+ clocks[2] = new ROS2PeerClockOffsetEstimator(ros2Node1);
+
+ ThreadTools.park(0.5);
+
+ for (int i = 0; i < 10; i++)
+ {
+ for (ROS2PeerClockOffsetEstimator clock : clocks)
+ Assertions.assertEquals(clocks.length - 1, clock.getPeerList().size());
+
+ for (ROS2PeerClockOffsetEstimator clock : clocks)
+ {
+ Duration offset = clock.getPeerList().get(0).getPeerClockOffset();
+ LogTools.info("Clock offset: {}", offset);
+ Assertions.assertTrue(TimeTools.toDoubleSeconds(offset) < 0.1);
+ }
+
+ for (ROS2PeerClockOffsetEstimator clock : clocks)
+ {
+ LogTools.info("Converted peer time: {}", clock.getPeerList().get(0).getPeerTimeInLocalFrame(Instant.now()));
+ }
+
+ ThreadTools.park(0.3);
+ }
+
+ for (ROS2PeerClockOffsetEstimator clock : clocks)
+ clock.destroy();
+
+ ros2Node0.destroy();
+ ros2Node1.destroy();
+ }
+}
diff --git a/ihmc-communication/src/test/resources/configurations/RDXROS2PeerClockTest/ImGuiPanels.json b/ihmc-communication/src/test/resources/configurations/RDXROS2PeerClockTest/ImGuiPanels.json
new file mode 100644
index 00000000000..33a24b29810
--- /dev/null
+++ b/ihmc-communication/src/test/resources/configurations/RDXROS2PeerClockTest/ImGuiPanels.json
@@ -0,0 +1,7 @@
+{
+ "dockspacePanels" : { },
+ "windows" : {
+ "3D View" : false,
+ "Main" : true
+ }
+}
\ No newline at end of file
diff --git a/ihmc-communication/src/test/resources/configurations/RDXROS2PeerClockTest/ImGuiSettings.ini b/ihmc-communication/src/test/resources/configurations/RDXROS2PeerClockTest/ImGuiSettings.ini
new file mode 100644
index 00000000000..ef4c5f7f64e
--- /dev/null
+++ b/ihmc-communication/src/test/resources/configurations/RDXROS2PeerClockTest/ImGuiSettings.ini
@@ -0,0 +1,24 @@
+[Window][DockSpaceViewport_11111111]
+Pos=0,22
+Size=800,578
+Collapsed=0
+
+[Window][Main]
+Pos=0,22
+Size=800,578
+Collapsed=0
+DockId=0x8B93E3BD,0
+
+[Window][Debug##Default]
+Pos=60,60
+Size=400,400
+Collapsed=0
+
+[Window][3D View]
+Pos=132,100
+Size=300,200
+Collapsed=0
+
+[Docking][Data]
+DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=73,88 Size=800,578 CentralNode=1 Selected=0x1F1A625A
+
diff --git a/ihmc-interfaces/src/main/generated-idl/ihmc_common_msgs/msg/GuidMessage_.idl b/ihmc-interfaces/src/main/generated-idl/ihmc_common_msgs/msg/GuidMessage_.idl
new file mode 100644
index 00000000000..1fe7bebf6d0
--- /dev/null
+++ b/ihmc-interfaces/src/main/generated-idl/ihmc_common_msgs/msg/GuidMessage_.idl
@@ -0,0 +1,30 @@
+#ifndef __ihmc_common_msgs__msg__GuidMessage__idl__
+#define __ihmc_common_msgs__msg__GuidMessage__idl__
+
+module ihmc_common_msgs
+{
+ module msg
+ {
+ module dds
+ {
+
+ /**
+ * Represents a DDS-RTPS Guid
+ */
+ @TypeCode(type="ihmc_common_msgs::msg::dds_::GuidMessage_")
+ struct GuidMessage
+ {
+ /**
+ * Prefix
+ */
+ octet prefix[12];
+ /**
+ * Entity
+ */
+ octet entity[4];
+ };
+ };
+ };
+};
+
+#endif
diff --git a/ihmc-interfaces/src/main/generated-idl/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage_.idl b/ihmc-interfaces/src/main/generated-idl/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage_.idl
new file mode 100644
index 00000000000..60c0f1a61b3
--- /dev/null
+++ b/ihmc-interfaces/src/main/generated-idl/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage_.idl
@@ -0,0 +1,45 @@
+#ifndef __ihmc_common_msgs__msg__PeerClockOffsetEstimatorPingMessage__idl__
+#define __ihmc_common_msgs__msg__PeerClockOffsetEstimatorPingMessage__idl__
+
+#include "ihmc_common_msgs/msg/./GuidMessage_.idl"
+#include "ihmc_common_msgs/msg/./InstantMessage_.idl"
+module ihmc_common_msgs
+{
+ module msg
+ {
+ module dds
+ {
+
+ /**
+ * This message is a request-reply ping used to estimate the clock offset
+ * of another node to be used by data synchronization algorithms.
+ */
+ @TypeCode(type="ihmc_common_msgs::msg::dds_::PeerClockOffsetEstimatorPingMessage_")
+ struct PeerClockOffsetEstimatorPingMessage
+ {
+ /**
+ * If this is a request message, else it's a reply message
+ */
+ boolean is_request;
+ /**
+ * The guid of the publisher that's expected to send back a reply
+ */
+ ihmc_common_msgs::msg::dds::GuidMessage request_target;
+ /**
+ * The guid of the publisher associated with the requester
+ */
+ ihmc_common_msgs::msg::dds::GuidMessage reply_target;
+ /**
+ * The time at which the request is sent
+ */
+ ihmc_common_msgs::msg::dds::InstantMessage request_send_time;
+ /**
+ * The time at which the reply is sent
+ */
+ ihmc_common_msgs::msg::dds::InstantMessage reply_send_time;
+ };
+ };
+ };
+};
+
+#endif
diff --git a/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/GuidMessage.java b/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/GuidMessage.java
new file mode 100644
index 00000000000..93616a3a20c
--- /dev/null
+++ b/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/GuidMessage.java
@@ -0,0 +1,139 @@
+package ihmc_common_msgs.msg.dds;
+
+import us.ihmc.communication.packets.Packet;
+import us.ihmc.euclid.interfaces.Settable;
+import us.ihmc.euclid.interfaces.EpsilonComparable;
+import java.util.function.Supplier;
+import us.ihmc.pubsub.TopicDataType;
+
+/**
+ * Represents a DDS-RTPS Guid
+ */
+public class GuidMessage extends Packet implements Settable, EpsilonComparable
+{
+ /**
+ * Prefix
+ */
+ public byte[] prefix_;
+ /**
+ * Entity
+ */
+ public byte[] entity_;
+
+ public GuidMessage()
+ {
+ prefix_ = new byte[12];
+
+ entity_ = new byte[4];
+
+ }
+
+ public GuidMessage(GuidMessage other)
+ {
+ this();
+ set(other);
+ }
+
+ public void set(GuidMessage other)
+ {
+ for(int i1 = 0; i1 < prefix_.length; ++i1)
+ {
+ prefix_[i1] = other.prefix_[i1];
+
+ }
+
+ for(int i3 = 0; i3 < entity_.length; ++i3)
+ {
+ entity_[i3] = other.entity_[i3];
+
+ }
+
+ }
+
+
+ /**
+ * Prefix
+ */
+ public byte[] getPrefix()
+ {
+ return prefix_;
+ }
+
+
+ /**
+ * Entity
+ */
+ public byte[] getEntity()
+ {
+ return entity_;
+ }
+
+
+ public static Supplier getPubSubType()
+ {
+ return GuidMessagePubSubType::new;
+ }
+
+ @Override
+ public Supplier getPubSubTypePacket()
+ {
+ return GuidMessagePubSubType::new;
+ }
+
+ @Override
+ public boolean epsilonEquals(GuidMessage other, double epsilon)
+ {
+ if(other == null) return false;
+ if(other == this) return true;
+
+ for(int i5 = 0; i5 < prefix_.length; ++i5)
+ {
+ if (!us.ihmc.idl.IDLTools.epsilonEqualsPrimitive(this.prefix_[i5], other.prefix_[i5], epsilon)) return false;
+ }
+
+ for(int i7 = 0; i7 < entity_.length; ++i7)
+ {
+ if (!us.ihmc.idl.IDLTools.epsilonEqualsPrimitive(this.entity_[i7], other.entity_[i7], epsilon)) return false;
+ }
+
+
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other)
+ {
+ if(other == null) return false;
+ if(other == this) return true;
+ if(!(other instanceof GuidMessage)) return false;
+
+ GuidMessage otherMyClass = (GuidMessage) other;
+
+ for(int i9 = 0; i9 < prefix_.length; ++i9)
+ {
+ if(this.prefix_[i9] != otherMyClass.prefix_[i9]) return false;
+
+ }
+ for(int i11 = 0; i11 < entity_.length; ++i11)
+ {
+ if(this.entity_[i11] != otherMyClass.entity_[i11]) return false;
+
+ }
+
+ return true;
+ }
+
+ @Override
+ public java.lang.String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append("GuidMessage {");
+ builder.append("prefix=");
+ builder.append(java.util.Arrays.toString(this.prefix_)); builder.append(", ");
+ builder.append("entity=");
+ builder.append(java.util.Arrays.toString(this.entity_));
+ builder.append("}");
+ return builder.toString();
+ }
+}
diff --git a/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/GuidMessagePubSubType.java b/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/GuidMessagePubSubType.java
new file mode 100644
index 00000000000..4bdeea0bf5d
--- /dev/null
+++ b/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/GuidMessagePubSubType.java
@@ -0,0 +1,165 @@
+package ihmc_common_msgs.msg.dds;
+
+/**
+*
+* Topic data type of the struct "GuidMessage" defined in "GuidMessage_.idl". Use this class to provide the TopicDataType to a Participant.
+*
+* This file was automatically generated from GuidMessage_.idl by us.ihmc.idl.generator.IDLGenerator.
+* Do not update this file directly, edit GuidMessage_.idl instead.
+*
+*/
+public class GuidMessagePubSubType implements us.ihmc.pubsub.TopicDataType
+{
+ public static final java.lang.String name = "ihmc_common_msgs::msg::dds_::GuidMessage_";
+
+ @Override
+ public final java.lang.String getDefinitionChecksum()
+ {
+ return "5f61ddf5c243dbf8995931ed6448c975b957a0835684ac6064ec5e810f93607d";
+ }
+
+ @Override
+ public final java.lang.String getDefinitionVersion()
+ {
+ return "local";
+ }
+
+ private final us.ihmc.idl.CDR serializeCDR = new us.ihmc.idl.CDR();
+ private final us.ihmc.idl.CDR deserializeCDR = new us.ihmc.idl.CDR();
+
+ @Override
+ public void serialize(ihmc_common_msgs.msg.dds.GuidMessage data, us.ihmc.pubsub.common.SerializedPayload serializedPayload) throws java.io.IOException
+ {
+ serializeCDR.serialize(serializedPayload);
+ write(data, serializeCDR);
+ serializeCDR.finishSerialize();
+ }
+
+ @Override
+ public void deserialize(us.ihmc.pubsub.common.SerializedPayload serializedPayload, ihmc_common_msgs.msg.dds.GuidMessage data) throws java.io.IOException
+ {
+ deserializeCDR.deserialize(serializedPayload);
+ read(data, deserializeCDR);
+ deserializeCDR.finishDeserialize();
+ }
+
+ public static int getMaxCdrSerializedSize()
+ {
+ return getMaxCdrSerializedSize(0);
+ }
+
+ public static int getMaxCdrSerializedSize(int current_alignment)
+ {
+ int initial_alignment = current_alignment;
+
+ current_alignment += ((12) * 1) + us.ihmc.idl.CDR.alignment(current_alignment, 1);
+
+ current_alignment += ((4) * 1) + us.ihmc.idl.CDR.alignment(current_alignment, 1);
+
+
+ return current_alignment - initial_alignment;
+ }
+
+ public final static int getCdrSerializedSize(ihmc_common_msgs.msg.dds.GuidMessage data)
+ {
+ return getCdrSerializedSize(data, 0);
+ }
+
+ public final static int getCdrSerializedSize(ihmc_common_msgs.msg.dds.GuidMessage data, int current_alignment)
+ {
+ int initial_alignment = current_alignment;
+
+ current_alignment += ((12) * 1) + us.ihmc.idl.CDR.alignment(current_alignment, 1);
+ current_alignment += ((4) * 1) + us.ihmc.idl.CDR.alignment(current_alignment, 1);
+
+ return current_alignment - initial_alignment;
+ }
+
+ public static void write(ihmc_common_msgs.msg.dds.GuidMessage data, us.ihmc.idl.CDR cdr)
+ {
+ for(int i0 = 0; i0 < data.getPrefix().length; ++i0)
+ {
+ cdr.write_type_9(data.getPrefix()[i0]);
+ }
+
+ for(int i0 = 0; i0 < data.getEntity().length; ++i0)
+ {
+ cdr.write_type_9(data.getEntity()[i0]);
+ }
+
+ }
+
+ public static void read(ihmc_common_msgs.msg.dds.GuidMessage data, us.ihmc.idl.CDR cdr)
+ {
+ for(int i0 = 0; i0 < data.getPrefix().length; ++i0)
+ {
+ data.getPrefix()[i0] = cdr.read_type_9();
+
+ }
+
+ for(int i0 = 0; i0 < data.getEntity().length; ++i0)
+ {
+ data.getEntity()[i0] = cdr.read_type_9();
+
+ }
+
+
+ }
+
+ @Override
+ public final void serialize(ihmc_common_msgs.msg.dds.GuidMessage data, us.ihmc.idl.InterchangeSerializer ser)
+ {
+ ser.write_type_f("prefix", data.getPrefix());
+ ser.write_type_f("entity", data.getEntity());
+ }
+
+ @Override
+ public final void deserialize(us.ihmc.idl.InterchangeSerializer ser, ihmc_common_msgs.msg.dds.GuidMessage data)
+ {
+ ser.read_type_f("prefix", data.getPrefix());
+ ser.read_type_f("entity", data.getEntity());
+ }
+
+ public static void staticCopy(ihmc_common_msgs.msg.dds.GuidMessage src, ihmc_common_msgs.msg.dds.GuidMessage dest)
+ {
+ dest.set(src);
+ }
+
+ @Override
+ public ihmc_common_msgs.msg.dds.GuidMessage createData()
+ {
+ return new ihmc_common_msgs.msg.dds.GuidMessage();
+ }
+ @Override
+ public int getTypeSize()
+ {
+ return us.ihmc.idl.CDR.getTypeSize(getMaxCdrSerializedSize());
+ }
+
+ @Override
+ public java.lang.String getName()
+ {
+ return name;
+ }
+
+ public void serialize(ihmc_common_msgs.msg.dds.GuidMessage data, us.ihmc.idl.CDR cdr)
+ {
+ write(data, cdr);
+ }
+
+ public void deserialize(ihmc_common_msgs.msg.dds.GuidMessage data, us.ihmc.idl.CDR cdr)
+ {
+ read(data, cdr);
+ }
+
+ public void copy(ihmc_common_msgs.msg.dds.GuidMessage src, ihmc_common_msgs.msg.dds.GuidMessage dest)
+ {
+ staticCopy(src, dest);
+ }
+
+ @Override
+ public GuidMessagePubSubType newInstance()
+ {
+ return new GuidMessagePubSubType();
+ }
+}
diff --git a/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/PeerClockOffsetEstimatorPingMessage.java b/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/PeerClockOffsetEstimatorPingMessage.java
new file mode 100644
index 00000000000..2f28591b13a
--- /dev/null
+++ b/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/PeerClockOffsetEstimatorPingMessage.java
@@ -0,0 +1,177 @@
+package ihmc_common_msgs.msg.dds;
+
+import us.ihmc.communication.packets.Packet;
+import us.ihmc.euclid.interfaces.Settable;
+import us.ihmc.euclid.interfaces.EpsilonComparable;
+import java.util.function.Supplier;
+import us.ihmc.pubsub.TopicDataType;
+
+/**
+ * This message is a request-reply ping used to estimate the clock offset
+ * of another node to be used by data synchronization algorithms.
+ */
+public class PeerClockOffsetEstimatorPingMessage extends Packet implements Settable, EpsilonComparable
+{
+ /**
+ * If this is a request message, else it's a reply message
+ */
+ public boolean is_request_;
+ /**
+ * The guid of the publisher that's expected to send back a reply
+ */
+ public ihmc_common_msgs.msg.dds.GuidMessage request_target_;
+ /**
+ * The guid of the publisher associated with the requester
+ */
+ public ihmc_common_msgs.msg.dds.GuidMessage reply_target_;
+ /**
+ * The time at which the request is sent
+ */
+ public ihmc_common_msgs.msg.dds.InstantMessage request_send_time_;
+ /**
+ * The time at which the reply is sent
+ */
+ public ihmc_common_msgs.msg.dds.InstantMessage reply_send_time_;
+
+ public PeerClockOffsetEstimatorPingMessage()
+ {
+ request_target_ = new ihmc_common_msgs.msg.dds.GuidMessage();
+ reply_target_ = new ihmc_common_msgs.msg.dds.GuidMessage();
+ request_send_time_ = new ihmc_common_msgs.msg.dds.InstantMessage();
+ reply_send_time_ = new ihmc_common_msgs.msg.dds.InstantMessage();
+ }
+
+ public PeerClockOffsetEstimatorPingMessage(PeerClockOffsetEstimatorPingMessage other)
+ {
+ this();
+ set(other);
+ }
+
+ public void set(PeerClockOffsetEstimatorPingMessage other)
+ {
+ is_request_ = other.is_request_;
+
+ ihmc_common_msgs.msg.dds.GuidMessagePubSubType.staticCopy(other.request_target_, request_target_);
+ ihmc_common_msgs.msg.dds.GuidMessagePubSubType.staticCopy(other.reply_target_, reply_target_);
+ ihmc_common_msgs.msg.dds.InstantMessagePubSubType.staticCopy(other.request_send_time_, request_send_time_);
+ ihmc_common_msgs.msg.dds.InstantMessagePubSubType.staticCopy(other.reply_send_time_, reply_send_time_);
+ }
+
+ /**
+ * If this is a request message, else it's a reply message
+ */
+ public void setIsRequest(boolean is_request)
+ {
+ is_request_ = is_request;
+ }
+ /**
+ * If this is a request message, else it's a reply message
+ */
+ public boolean getIsRequest()
+ {
+ return is_request_;
+ }
+
+
+ /**
+ * The guid of the publisher that's expected to send back a reply
+ */
+ public ihmc_common_msgs.msg.dds.GuidMessage getRequestTarget()
+ {
+ return request_target_;
+ }
+
+
+ /**
+ * The guid of the publisher associated with the requester
+ */
+ public ihmc_common_msgs.msg.dds.GuidMessage getReplyTarget()
+ {
+ return reply_target_;
+ }
+
+
+ /**
+ * The time at which the request is sent
+ */
+ public ihmc_common_msgs.msg.dds.InstantMessage getRequestSendTime()
+ {
+ return request_send_time_;
+ }
+
+
+ /**
+ * The time at which the reply is sent
+ */
+ public ihmc_common_msgs.msg.dds.InstantMessage getReplySendTime()
+ {
+ return reply_send_time_;
+ }
+
+
+ public static Supplier getPubSubType()
+ {
+ return PeerClockOffsetEstimatorPingMessagePubSubType::new;
+ }
+
+ @Override
+ public Supplier getPubSubTypePacket()
+ {
+ return PeerClockOffsetEstimatorPingMessagePubSubType::new;
+ }
+
+ @Override
+ public boolean epsilonEquals(PeerClockOffsetEstimatorPingMessage other, double epsilon)
+ {
+ if(other == null) return false;
+ if(other == this) return true;
+
+ if (!us.ihmc.idl.IDLTools.epsilonEqualsBoolean(this.is_request_, other.is_request_, epsilon)) return false;
+
+ if (!this.request_target_.epsilonEquals(other.request_target_, epsilon)) return false;
+ if (!this.reply_target_.epsilonEquals(other.reply_target_, epsilon)) return false;
+ if (!this.request_send_time_.epsilonEquals(other.request_send_time_, epsilon)) return false;
+ if (!this.reply_send_time_.epsilonEquals(other.reply_send_time_, epsilon)) return false;
+
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other)
+ {
+ if(other == null) return false;
+ if(other == this) return true;
+ if(!(other instanceof PeerClockOffsetEstimatorPingMessage)) return false;
+
+ PeerClockOffsetEstimatorPingMessage otherMyClass = (PeerClockOffsetEstimatorPingMessage) other;
+
+ if(this.is_request_ != otherMyClass.is_request_) return false;
+
+ if (!this.request_target_.equals(otherMyClass.request_target_)) return false;
+ if (!this.reply_target_.equals(otherMyClass.reply_target_)) return false;
+ if (!this.request_send_time_.equals(otherMyClass.request_send_time_)) return false;
+ if (!this.reply_send_time_.equals(otherMyClass.reply_send_time_)) return false;
+
+ return true;
+ }
+
+ @Override
+ public java.lang.String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append("PeerClockOffsetEstimatorPingMessage {");
+ builder.append("is_request=");
+ builder.append(this.is_request_); builder.append(", ");
+ builder.append("request_target=");
+ builder.append(this.request_target_); builder.append(", ");
+ builder.append("reply_target=");
+ builder.append(this.reply_target_); builder.append(", ");
+ builder.append("request_send_time=");
+ builder.append(this.request_send_time_); builder.append(", ");
+ builder.append("reply_send_time=");
+ builder.append(this.reply_send_time_);
+ builder.append("}");
+ return builder.toString();
+ }
+}
diff --git a/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/PeerClockOffsetEstimatorPingMessagePubSubType.java b/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/PeerClockOffsetEstimatorPingMessagePubSubType.java
new file mode 100644
index 00000000000..793f94d10cf
--- /dev/null
+++ b/ihmc-interfaces/src/main/generated-java/ihmc_common_msgs/msg/dds/PeerClockOffsetEstimatorPingMessagePubSubType.java
@@ -0,0 +1,184 @@
+package ihmc_common_msgs.msg.dds;
+
+/**
+*
+* Topic data type of the struct "PeerClockOffsetEstimatorPingMessage" defined in "PeerClockOffsetEstimatorPingMessage_.idl". Use this class to provide the TopicDataType to a Participant.
+*
+* This file was automatically generated from PeerClockOffsetEstimatorPingMessage_.idl by us.ihmc.idl.generator.IDLGenerator.
+* Do not update this file directly, edit PeerClockOffsetEstimatorPingMessage_.idl instead.
+*
+*/
+public class PeerClockOffsetEstimatorPingMessagePubSubType implements us.ihmc.pubsub.TopicDataType
+{
+ public static final java.lang.String name = "ihmc_common_msgs::msg::dds_::PeerClockOffsetEstimatorPingMessage_";
+
+ @Override
+ public final java.lang.String getDefinitionChecksum()
+ {
+ return "d21275a34208cc47d453c10c1d2d01990ee182f4a62b2de9c99f880e6b2bf166";
+ }
+
+ @Override
+ public final java.lang.String getDefinitionVersion()
+ {
+ return "local";
+ }
+
+ private final us.ihmc.idl.CDR serializeCDR = new us.ihmc.idl.CDR();
+ private final us.ihmc.idl.CDR deserializeCDR = new us.ihmc.idl.CDR();
+
+ @Override
+ public void serialize(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data, us.ihmc.pubsub.common.SerializedPayload serializedPayload) throws java.io.IOException
+ {
+ serializeCDR.serialize(serializedPayload);
+ write(data, serializeCDR);
+ serializeCDR.finishSerialize();
+ }
+
+ @Override
+ public void deserialize(us.ihmc.pubsub.common.SerializedPayload serializedPayload, ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data) throws java.io.IOException
+ {
+ deserializeCDR.deserialize(serializedPayload);
+ read(data, deserializeCDR);
+ deserializeCDR.finishDeserialize();
+ }
+
+ public static int getMaxCdrSerializedSize()
+ {
+ return getMaxCdrSerializedSize(0);
+ }
+
+ public static int getMaxCdrSerializedSize(int current_alignment)
+ {
+ int initial_alignment = current_alignment;
+
+ current_alignment += 1 + us.ihmc.idl.CDR.alignment(current_alignment, 1);
+
+ current_alignment += ihmc_common_msgs.msg.dds.GuidMessagePubSubType.getMaxCdrSerializedSize(current_alignment);
+
+ current_alignment += ihmc_common_msgs.msg.dds.GuidMessagePubSubType.getMaxCdrSerializedSize(current_alignment);
+
+ current_alignment += ihmc_common_msgs.msg.dds.InstantMessagePubSubType.getMaxCdrSerializedSize(current_alignment);
+
+ current_alignment += ihmc_common_msgs.msg.dds.InstantMessagePubSubType.getMaxCdrSerializedSize(current_alignment);
+
+
+ return current_alignment - initial_alignment;
+ }
+
+ public final static int getCdrSerializedSize(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data)
+ {
+ return getCdrSerializedSize(data, 0);
+ }
+
+ public final static int getCdrSerializedSize(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data, int current_alignment)
+ {
+ int initial_alignment = current_alignment;
+
+ current_alignment += 1 + us.ihmc.idl.CDR.alignment(current_alignment, 1);
+
+
+ current_alignment += ihmc_common_msgs.msg.dds.GuidMessagePubSubType.getCdrSerializedSize(data.getRequestTarget(), current_alignment);
+
+ current_alignment += ihmc_common_msgs.msg.dds.GuidMessagePubSubType.getCdrSerializedSize(data.getReplyTarget(), current_alignment);
+
+ current_alignment += ihmc_common_msgs.msg.dds.InstantMessagePubSubType.getCdrSerializedSize(data.getRequestSendTime(), current_alignment);
+
+ current_alignment += ihmc_common_msgs.msg.dds.InstantMessagePubSubType.getCdrSerializedSize(data.getReplySendTime(), current_alignment);
+
+
+ return current_alignment - initial_alignment;
+ }
+
+ public static void write(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data, us.ihmc.idl.CDR cdr)
+ {
+ cdr.write_type_7(data.getIsRequest());
+
+ ihmc_common_msgs.msg.dds.GuidMessagePubSubType.write(data.getRequestTarget(), cdr);
+ ihmc_common_msgs.msg.dds.GuidMessagePubSubType.write(data.getReplyTarget(), cdr);
+ ihmc_common_msgs.msg.dds.InstantMessagePubSubType.write(data.getRequestSendTime(), cdr);
+ ihmc_common_msgs.msg.dds.InstantMessagePubSubType.write(data.getReplySendTime(), cdr);
+ }
+
+ public static void read(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data, us.ihmc.idl.CDR cdr)
+ {
+ data.setIsRequest(cdr.read_type_7());
+
+ ihmc_common_msgs.msg.dds.GuidMessagePubSubType.read(data.getRequestTarget(), cdr);
+ ihmc_common_msgs.msg.dds.GuidMessagePubSubType.read(data.getReplyTarget(), cdr);
+ ihmc_common_msgs.msg.dds.InstantMessagePubSubType.read(data.getRequestSendTime(), cdr);
+ ihmc_common_msgs.msg.dds.InstantMessagePubSubType.read(data.getReplySendTime(), cdr);
+
+ }
+
+ @Override
+ public final void serialize(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data, us.ihmc.idl.InterchangeSerializer ser)
+ {
+ ser.write_type_7("is_request", data.getIsRequest());
+ ser.write_type_a("request_target", new ihmc_common_msgs.msg.dds.GuidMessagePubSubType(), data.getRequestTarget());
+
+ ser.write_type_a("reply_target", new ihmc_common_msgs.msg.dds.GuidMessagePubSubType(), data.getReplyTarget());
+
+ ser.write_type_a("request_send_time", new ihmc_common_msgs.msg.dds.InstantMessagePubSubType(), data.getRequestSendTime());
+
+ ser.write_type_a("reply_send_time", new ihmc_common_msgs.msg.dds.InstantMessagePubSubType(), data.getReplySendTime());
+
+ }
+
+ @Override
+ public final void deserialize(us.ihmc.idl.InterchangeSerializer ser, ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data)
+ {
+ data.setIsRequest(ser.read_type_7("is_request"));
+ ser.read_type_a("request_target", new ihmc_common_msgs.msg.dds.GuidMessagePubSubType(), data.getRequestTarget());
+
+ ser.read_type_a("reply_target", new ihmc_common_msgs.msg.dds.GuidMessagePubSubType(), data.getReplyTarget());
+
+ ser.read_type_a("request_send_time", new ihmc_common_msgs.msg.dds.InstantMessagePubSubType(), data.getRequestSendTime());
+
+ ser.read_type_a("reply_send_time", new ihmc_common_msgs.msg.dds.InstantMessagePubSubType(), data.getReplySendTime());
+
+ }
+
+ public static void staticCopy(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage src, ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage dest)
+ {
+ dest.set(src);
+ }
+
+ @Override
+ public ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage createData()
+ {
+ return new ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage();
+ }
+ @Override
+ public int getTypeSize()
+ {
+ return us.ihmc.idl.CDR.getTypeSize(getMaxCdrSerializedSize());
+ }
+
+ @Override
+ public java.lang.String getName()
+ {
+ return name;
+ }
+
+ public void serialize(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data, us.ihmc.idl.CDR cdr)
+ {
+ write(data, cdr);
+ }
+
+ public void deserialize(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage data, us.ihmc.idl.CDR cdr)
+ {
+ read(data, cdr);
+ }
+
+ public void copy(ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage src, ihmc_common_msgs.msg.dds.PeerClockOffsetEstimatorPingMessage dest)
+ {
+ staticCopy(src, dest);
+ }
+
+ @Override
+ public PeerClockOffsetEstimatorPingMessagePubSubType newInstance()
+ {
+ return new PeerClockOffsetEstimatorPingMessagePubSubType();
+ }
+}
diff --git a/ihmc-interfaces/src/main/messages/ihmc_interfaces/ihmc_common_msgs/msg/GuidMessage.msg b/ihmc-interfaces/src/main/messages/ihmc_interfaces/ihmc_common_msgs/msg/GuidMessage.msg
new file mode 100644
index 00000000000..427739a9e45
--- /dev/null
+++ b/ihmc-interfaces/src/main/messages/ihmc_interfaces/ihmc_common_msgs/msg/GuidMessage.msg
@@ -0,0 +1,7 @@
+# Represents a DDS-RTPS Guid
+
+# Prefix
+byte[12] prefix
+
+# Entity
+byte[4] entity
diff --git a/ihmc-interfaces/src/main/messages/ihmc_interfaces/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage.msg b/ihmc-interfaces/src/main/messages/ihmc_interfaces/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage.msg
new file mode 100644
index 00000000000..4da98d6c8fc
--- /dev/null
+++ b/ihmc-interfaces/src/main/messages/ihmc_interfaces/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage.msg
@@ -0,0 +1,17 @@
+# This message is a request-reply ping used to estimate the clock offset
+# of another node to be used by data synchronization algorithms.
+
+# If this is a request message, else it's a reply message
+bool is_request
+
+# The guid of the publisher that's expected to send back a reply
+ihmc_common_msgs/GuidMessage request_target
+
+# The guid of the publisher associated with the requester
+ihmc_common_msgs/GuidMessage reply_target
+
+# The time at which the request is sent
+ihmc_common_msgs/InstantMessage request_send_time
+
+# The time at which the reply is sent
+ihmc_common_msgs/InstantMessage reply_send_time
diff --git a/ihmc-interfaces/src/main/messages/ros1/ihmc_common_msgs/msg/GuidMessage.msg b/ihmc-interfaces/src/main/messages/ros1/ihmc_common_msgs/msg/GuidMessage.msg
new file mode 100644
index 00000000000..a367a4b165e
--- /dev/null
+++ b/ihmc-interfaces/src/main/messages/ros1/ihmc_common_msgs/msg/GuidMessage.msg
@@ -0,0 +1,9 @@
+# Represents a DDS-RTPS Guid
+
+# Prefix
+int8[] prefix
+
+# Entity
+int8[] entity
+
+
diff --git a/ihmc-interfaces/src/main/messages/ros1/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage.msg b/ihmc-interfaces/src/main/messages/ros1/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage.msg
new file mode 100644
index 00000000000..a035fc68c86
--- /dev/null
+++ b/ihmc-interfaces/src/main/messages/ros1/ihmc_common_msgs/msg/PeerClockOffsetEstimatorPingMessage.msg
@@ -0,0 +1,19 @@
+# This message is a request-reply ping used to estimate the clock offset
+# of another node to be used by data synchronization algorithms.
+
+# If this is a request message, else it's a reply message
+bool is_request
+
+# The guid of the publisher that's expected to send back a reply
+ihmc_common_msgs/GuidMessage request_target
+
+# The guid of the publisher associated with the requester
+ihmc_common_msgs/GuidMessage reply_target
+
+# The time at which the request is sent
+ihmc_common_msgs/InstantMessage request_send_time
+
+# The time at which the reply is sent
+ihmc_common_msgs/InstantMessage reply_send_time
+
+
From b3100f832e1c54e9cd5ff4c289bae36d2762b68e Mon Sep 17 00:00:00 2001
From: ds58 <30220598+ds58@users.noreply.github.com>
Date: Thu, 19 Dec 2024 11:56:23 -0600
Subject: [PATCH 14/16] :arrow_up: ihmc-ros2-library 1.1.5 (#550)
---
ihmc-communication/build.gradle.kts | 4 ++--
ihmc-interfaces/build.gradle.kts | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/ihmc-communication/build.gradle.kts b/ihmc-communication/build.gradle.kts
index 96cbbf1e895..824e272a05e 100644
--- a/ihmc-communication/build.gradle.kts
+++ b/ihmc-communication/build.gradle.kts
@@ -13,7 +13,7 @@ ihmc {
mainDependencies {
api("us.ihmc:ihmc-realtime:1.7.0")
api("us.ihmc:ihmc-video-codecs:2.1.6")
- api("us.ihmc:ros2-library:1.1.4")
+ api("us.ihmc:ros2-library:1.1.5")
api("commons-net:commons-net:3.6")
api("org.lz4:lz4-java:1.8.0")
@@ -25,5 +25,5 @@ mainDependencies {
testDependencies {
api("us.ihmc:ihmc-robotics-toolkit-test:source")
api("us.ihmc:ihmc-graphics-libgdx:source")
- api("us.ihmc:ros2-library-test:1.1.4")
+ api("us.ihmc:ros2-library-test:1.1.5")
}
diff --git a/ihmc-interfaces/build.gradle.kts b/ihmc-interfaces/build.gradle.kts
index e5f2fbcb7d9..bffb6aeedb2 100644
--- a/ihmc-interfaces/build.gradle.kts
+++ b/ihmc-interfaces/build.gradle.kts
@@ -27,8 +27,8 @@ ihmc {
mainDependencies {
api("us.ihmc:euclid-geometry:0.21.0")
- api("us.ihmc:ihmc-pub-sub:1.1.4")
- api("us.ihmc:ros2-common-interfaces:1.1.4") {
+ api("us.ihmc:ihmc-pub-sub:1.1.5")
+ api("us.ihmc:ros2-common-interfaces:1.1.5") {
exclude(group = "org.junit.jupiter", module = "junit-jupiter-api")
exclude(group = "org.junit.jupiter", module = "junit-jupiter-engine")
exclude(group = "org.junit.platform", module = "junit-platform-commons")
@@ -38,13 +38,13 @@ mainDependencies {
}
testDependencies {
- api("us.ihmc:ros2-library:1.1.4")
+ api("us.ihmc:ros2-library:1.1.5")
}
generatorDependencies {
api("us.ihmc:euclid:0.21.0")
api("us.ihmc:ihmc-commons:0.35.0")
- api("us.ihmc:ros2-msg-to-pubsub-generator:1.1.4")
+ api("us.ihmc:ros2-msg-to-pubsub-generator:1.1.5")
}
val generator = us.ihmc.ros2.rosidl.ROS2InterfaceGenerator()
From d460ab675418f4861dd11d9dcbcca0eaeac58c0c Mon Sep 17 00:00:00 2001
From: Nick Kitchel <59104880+PotatoPeeler3000@users.noreply.github.com>
Date: Thu, 19 Dec 2024 13:21:50 -0600
Subject: [PATCH 15/16] Support OpenCL and CUDA within the
RapidHeightMapManager (#545)
* Created a RapidHeightMapExtractorInterface so that the manager can use CUDA or OpenCL depending on the hardware of the machine
---
.../MonteCarloFootstepPlanningTest.java | 9 +++-
.../perception/HumanoidPerceptionModule.java | 5 +-
.../perception/RapidHeightMapManager.java | 54 +++++++++++++++----
.../RDXRapidHeightMapExtractorCUDADemo.java | 3 +-
.../gpuHeightMap/RapidHeightMapExtractor.java | 35 ++++++------
.../RapidHeightMapExtractorCUDA.java | 35 ++++++------
.../RapidHeightMapExtractorInterface.java | 24 +++++++++
7 files changed, 111 insertions(+), 54 deletions(-)
create mode 100644 ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractorInterface.java
diff --git a/ihmc-footstep-planning/src/test/java/us/ihmc/footstepPlanning/monteCarloPlanning/MonteCarloFootstepPlanningTest.java b/ihmc-footstep-planning/src/test/java/us/ihmc/footstepPlanning/monteCarloPlanning/MonteCarloFootstepPlanningTest.java
index d8301481ef9..b51b4769516 100644
--- a/ihmc-footstep-planning/src/test/java/us/ihmc/footstepPlanning/monteCarloPlanning/MonteCarloFootstepPlanningTest.java
+++ b/ihmc-footstep-planning/src/test/java/us/ihmc/footstepPlanning/monteCarloPlanning/MonteCarloFootstepPlanningTest.java
@@ -1,5 +1,7 @@
package us.ihmc.footstepPlanning.monteCarloPlanning;
+import org.bytedeco.opencl.global.OpenCL;
+import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.opencv_core.Mat;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
@@ -13,6 +15,7 @@
import us.ihmc.footstepPlanning.tools.HeightMapTerrainGeneratorTools;
import us.ihmc.footstepPlanning.tools.PlannerTools;
import us.ihmc.log.LogTools;
+import us.ihmc.perception.BytedecoImage;
import us.ihmc.perception.camera.CameraIntrinsics;
import us.ihmc.perception.gpuHeightMap.RapidHeightMapExtractor;
import us.ihmc.perception.heightMap.TerrainMapData;
@@ -28,12 +31,16 @@ public class MonteCarloFootstepPlanningTest
// private TerrainPlanningDebugger debugger = new TerrainPlanningDebugger(null, plannerParameters);
private MonteCarloFootstepPlanner planner = new MonteCarloFootstepPlanner(plannerParameters, PlannerTools.createFootPolygons(0.2, 0.1, 0.08));
private CameraIntrinsics cameraIntrinsics = new CameraIntrinsics();
- private RapidHeightMapExtractor heightMapExtractor = new RapidHeightMapExtractor(openCLManager);
@Disabled
@Test
public void testMonteCarloFootstepPlanning()
{
+ CameraIntrinsics depthImageIntrinsics = new CameraIntrinsics();
+ BytedecoImage heightMapBytedecoImage = new BytedecoImage(depthImageIntrinsics.getWidth(), depthImageIntrinsics.getHeight(), opencv_core.CV_16UC1);
+ heightMapBytedecoImage.createOpenCLImage(openCLManager, OpenCL.CL_MEM_READ_WRITE);
+ RapidHeightMapExtractor heightMapExtractor = new RapidHeightMapExtractor(openCLManager, heightMapBytedecoImage, 1);
+
LogTools.info("Initializing");
RapidHeightMapExtractor.getHeightMapParameters().setInternalGlobalWidthInMeters(4.0);
diff --git a/ihmc-high-level-behaviors/src/main/java/us/ihmc/perception/HumanoidPerceptionModule.java b/ihmc-high-level-behaviors/src/main/java/us/ihmc/perception/HumanoidPerceptionModule.java
index 0f30237c733..b2a3403dd67 100644
--- a/ihmc-high-level-behaviors/src/main/java/us/ihmc/perception/HumanoidPerceptionModule.java
+++ b/ihmc-high-level-behaviors/src/main/java/us/ihmc/perception/HumanoidPerceptionModule.java
@@ -313,9 +313,10 @@ public void initializeHeightMapExtractor(ROS2Helper ros2Helper, HumanoidReferenc
LogTools.info("Rapid Height Map: {}", cameraIntrinsics);
rapidHeightMapExtractor = new RapidHeightMapExtractor(openCLManager,
referenceFrames.getSoleFrame(RobotSide.LEFT),
- referenceFrames.getSoleFrame(RobotSide.RIGHT));
+ referenceFrames.getSoleFrame(RobotSide.RIGHT),
+ realsenseDepthImage,
+ 1);
rapidHeightMapExtractor.setDepthIntrinsics(cameraIntrinsics);
- rapidHeightMapExtractor.create(realsenseDepthImage, 1);
if (ros2Helper != null)
ros2Helper.subscribeViaVolatileCallback(PerceptionAPI.RESET_HEIGHT_MAP, message -> resetHeightMapRequested.set());
diff --git a/ihmc-high-level-behaviors/src/main/java/us/ihmc/perception/RapidHeightMapManager.java b/ihmc-high-level-behaviors/src/main/java/us/ihmc/perception/RapidHeightMapManager.java
index 385cb0fe0b1..d1b3f067f2f 100644
--- a/ihmc-high-level-behaviors/src/main/java/us/ihmc/perception/RapidHeightMapManager.java
+++ b/ihmc-high-level-behaviors/src/main/java/us/ihmc/perception/RapidHeightMapManager.java
@@ -2,6 +2,7 @@
import controller_msgs.msg.dds.HighLevelStateChangeStatusMessage;
import org.bytedeco.javacpp.BytePointer;
+import org.bytedeco.opencl.global.OpenCL;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.opencv_core.GpuMat;
import org.bytedeco.opencv.opencv_core.Mat;
@@ -15,8 +16,10 @@
import us.ihmc.euclid.referenceFrame.ReferenceFrame;
import us.ihmc.euclid.transform.RigidBodyTransform;
import us.ihmc.perception.camera.CameraIntrinsics;
+import us.ihmc.perception.cuda.CUDATools;
import us.ihmc.perception.gpuHeightMap.RapidHeightMapExtractor;
import us.ihmc.perception.gpuHeightMap.RapidHeightMapExtractorCUDA;
+import us.ihmc.perception.gpuHeightMap.RapidHeightMapExtractorInterface;
import us.ihmc.perception.heightMap.TerrainMapData;
import us.ihmc.perception.opencl.OpenCLManager;
import us.ihmc.perception.opencv.OpenCVTools;
@@ -30,16 +33,19 @@
*/
public class RapidHeightMapManager
{
- private final RapidHeightMapExtractorCUDA rapidHeightMapExtractor;
+ private final RapidHeightMapExtractorInterface rapidHeightMapExtractor;
private final ImageMessage croppedHeightMapImageMessage = new ImageMessage();
private final FramePose3D cameraPoseForHeightMap = new FramePose3D();
private final RigidBodyTransform sensorToWorldForHeightMap = new RigidBodyTransform();
private final RigidBodyTransform sensorToGroundForHeightMap = new RigidBodyTransform();
private final RigidBodyTransform groundToWorldForHeightMap = new RigidBodyTransform();
- private final GpuMat deviceDepthImage;
+ private GpuMat deviceDepthImage;
private final Mat hostDepthImage = new Mat();
+ private BytedecoImage heightMapBytedecoImage;
+
private final Notification resetHeightMapRequested = new Notification();
private final BytePointer compressedCroppedHeightMapPointer = new BytePointer();
+ private final boolean hasCUDAAvailable = CUDATools.hasCUDA();
public RapidHeightMapManager(OpenCLManager openCLManager,
DRCRobotModel robotModel,
@@ -48,11 +54,19 @@ public RapidHeightMapManager(OpenCLManager openCLManager,
CameraIntrinsics depthImageIntrinsics,
ROS2PublishSubscribeAPI ros2)
{
- rapidHeightMapExtractor = new RapidHeightMapExtractorCUDA(leftFootSoleFrame, rightFootSoleFrame);
- rapidHeightMapExtractor.setDepthIntrinsics(depthImageIntrinsics);
+ if (hasCUDAAvailable)
+ {
+ deviceDepthImage = new GpuMat(depthImageIntrinsics.getWidth(), depthImageIntrinsics.getHeight(), opencv_core.CV_16UC1);
+ rapidHeightMapExtractor = new RapidHeightMapExtractorCUDA(leftFootSoleFrame, rightFootSoleFrame, deviceDepthImage, 1);
+ }
+ else
+ {
+ heightMapBytedecoImage = new BytedecoImage(depthImageIntrinsics.getWidth(), depthImageIntrinsics.getHeight(), opencv_core.CV_16UC1);
+ heightMapBytedecoImage.createOpenCLImage(openCLManager, OpenCL.CL_MEM_READ_WRITE);
+ rapidHeightMapExtractor = new RapidHeightMapExtractor(openCLManager, leftFootSoleFrame, rightFootSoleFrame, heightMapBytedecoImage, 1);
+ }
- deviceDepthImage = new GpuMat(depthImageIntrinsics.getWidth(), depthImageIntrinsics.getHeight(), opencv_core.CV_16UC1);
- rapidHeightMapExtractor.create(deviceDepthImage, 1);
+ rapidHeightMapExtractor.setDepthIntrinsics(depthImageIntrinsics);
// We use a notification in order to only call resetting the height map in one place
ros2.subscribeViaVolatileCallback(PerceptionAPI.RESET_HEIGHT_MAP, message -> resetHeightMapRequested.set());
@@ -72,12 +86,30 @@ public void update(Mat latestDepthImage,
ReferenceFrame d455ZUpSensorFrame,
ROS2PublishSubscribeAPI ros2)
{
- if (latestDepthImage.type() == opencv_core.CV_32FC1) // Support our simulated sensors
- OpenCVTools.convertFloatToShort(latestDepthImage, hostDepthImage, 1000.0, 0.0);
+ if (hasCUDAAvailable)
+ {
+ if (latestDepthImage.type() == opencv_core.CV_32FC1) // Support our simulated sensors
+ {
+ OpenCVTools.convertFloatToShort(latestDepthImage, hostDepthImage, 1000.0, 0.0);
+ }
+ else
+ {
+ latestDepthImage.convertTo(hostDepthImage, opencv_core.CV_16UC1);
+ }
+
+ deviceDepthImage.upload(hostDepthImage);
+ }
else
- latestDepthImage.convertTo(hostDepthImage, opencv_core.CV_16UC1);
-
- deviceDepthImage.upload(hostDepthImage);
+ {
+ if (latestDepthImage.type() == opencv_core.CV_32FC1) // Support our simulated sensors
+ {
+ OpenCVTools.convertFloatToShort(latestDepthImage, heightMapBytedecoImage.getBytedecoOpenCVMat(), 1000.0, 0.0);
+ }
+ else
+ {
+ latestDepthImage.convertTo(heightMapBytedecoImage.getBytedecoOpenCVMat(), opencv_core.CV_16UC1);
+ }
+ }
if (resetHeightMapRequested.poll())
{
diff --git a/ihmc-high-level-behaviors/src/test/java/us/ihmc/rdx/perception/RDXRapidHeightMapExtractorCUDADemo.java b/ihmc-high-level-behaviors/src/test/java/us/ihmc/rdx/perception/RDXRapidHeightMapExtractorCUDADemo.java
index 473fb4f793e..ea08b7aa4bc 100644
--- a/ihmc-high-level-behaviors/src/test/java/us/ihmc/rdx/perception/RDXRapidHeightMapExtractorCUDADemo.java
+++ b/ihmc-high-level-behaviors/src/test/java/us/ihmc/rdx/perception/RDXRapidHeightMapExtractorCUDADemo.java
@@ -94,9 +94,8 @@ public void create()
heightMapImage = new GpuMat(intrinsics.getWidth(), intrinsics.getHeight(), opencv_core.CV_16UC1);
- extractor = new RapidHeightMapExtractorCUDA(leftFootSoleFrame, rightFootSoleFrame);
+ extractor = new RapidHeightMapExtractorCUDA(leftFootSoleFrame, rightFootSoleFrame, heightMapImage, 1);
extractor.setDepthIntrinsics(intrinsics);
- extractor.create(heightMapImage, 1);
baseUI.getPrimaryScene().addRenderableProvider(heightMapGraphic, RDXSceneLevel.MODEL);
}
diff --git a/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractor.java b/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractor.java
index d49ca09bbdf..9e3c508f185 100644
--- a/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractor.java
+++ b/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractor.java
@@ -39,7 +39,7 @@
* for distance away from boundaries and edges for each cell). For more information on Distance Transform visit:
* https://en.wikipedia.org/wiki/Distance_transform
* */
-public class RapidHeightMapExtractor
+public class RapidHeightMapExtractor implements RapidHeightMapExtractorInterface
{
private int mode = 1; // 0 -> Ouster, 1 -> Realsense
private float gridOffsetX;
@@ -119,18 +119,24 @@ public class RapidHeightMapExtractor
private Mat denoisedHeightMapImage;
private Rect cropWindowRectangle;
- public RapidHeightMapExtractor(OpenCLManager openCLManager)
+ public RapidHeightMapExtractor(OpenCLManager openCLManager, ReferenceFrame leftFootSoleFrame, ReferenceFrame rightFootSoleFrame, BytedecoImage depthImage, int mode)
{
- this.openCLManager = openCLManager;
- // denoiser = new HeightMapAutoencoder();
- rapidHeightMapUpdaterProgram = openCLManager.loadProgram("RapidHeightMapExtractor", "HeightMapUtils.cl");
+ this(openCLManager, depthImage, mode);
+ footSoleFrames.put(RobotSide.LEFT, leftFootSoleFrame);
+ footSoleFrames.put(RobotSide.RIGHT, rightFootSoleFrame);
}
- public RapidHeightMapExtractor(OpenCLManager openCLManager, ReferenceFrame leftFootSoleFrame, ReferenceFrame rightFootSoleFrame)
+ public RapidHeightMapExtractor(OpenCLManager openCLManager, BytedecoImage depthImage, int mode)
{
- this(openCLManager);
- footSoleFrames.put(RobotSide.LEFT, leftFootSoleFrame);
- footSoleFrames.put(RobotSide.RIGHT, rightFootSoleFrame);
+ this.openCLManager = openCLManager;
+
+ rapidHeightMapUpdaterProgram = openCLManager.loadProgram("RapidHeightMapExtractor", "HeightMapUtils.cl");
+
+ this.inputDepthImage = depthImage;
+ this.mode = mode;
+
+ initialize();
+ reset();
}
public void initialize()
@@ -187,15 +193,6 @@ public void initialize()
}
}
- public void create(BytedecoImage depthImage, int mode)
- {
- this.inputDepthImage = depthImage;
- this.mode = mode;
-
- initialize();
- reset();
- }
-
public void recomputeDerivedParameters()
{
centerIndex = HeightMapTools.computeCenterIndex(heightMapParameters.getLocalWidthInMeters(), heightMapParameters.getLocalCellSizeInMeters());
@@ -796,7 +793,7 @@ public Mat getCroppedImage(Point3DReadOnly origin, int globalCenterIndex, Mat im
return imageToCrop.apply(cropWindowRectangle);
}
- public static HeightMapData packHeightMapData(RapidHeightMapExtractor heightMapExtractor, HeightMapData heightMapDataToPack)
+ public static HeightMapData packHeightMapData(RapidHeightMapExtractorInterface heightMapExtractor, HeightMapData heightMapDataToPack)
{
Mat heightMapMat = heightMapExtractor.getTerrainMapData().getHeightMap();
HeightMapData latestHeightMapData = heightMapDataToPack;
diff --git a/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractorCUDA.java b/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractorCUDA.java
index 071c7e67061..998053bf8a5 100644
--- a/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractorCUDA.java
+++ b/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractorCUDA.java
@@ -33,7 +33,7 @@
import static org.bytedeco.cuda.global.cudart.cudaFree;
import static org.bytedeco.cuda.global.cudart.cudaStreamSynchronize;
-public class RapidHeightMapExtractorCUDA
+public class RapidHeightMapExtractorCUDA implements RapidHeightMapExtractorInterface
{
private static final int BLOCK_SIZE_XY = 32;
private static HeightMapParameters heightMapParameters = new HeightMapParameters("GPU");
@@ -112,10 +112,23 @@ public class RapidHeightMapExtractorCUDA
private float[] paramsArray;
- public RapidHeightMapExtractorCUDA(ReferenceFrame leftFootSoleFrame, ReferenceFrame rightFootSoleFrame)
+ public RapidHeightMapExtractorCUDA(ReferenceFrame leftFootSoleFrame, ReferenceFrame rightFootSoleFrame, GpuMat depthImage, int mode)
{
footSoleFrames.put(RobotSide.LEFT, leftFootSoleFrame);
footSoleFrames.put(RobotSide.RIGHT, rightFootSoleFrame);
+
+ inputDepthImage = depthImage;
+ this.mode = mode;
+
+ try
+ {
+ initialize();
+ }
+ catch (URISyntaxException e)
+ {
+ throw new RuntimeException(e);
+ }
+ reset();
}
public void setDepthIntrinsics(CameraIntrinsics cameraIntrinsics)
@@ -248,22 +261,6 @@ public void reset()
sequenceNumber = 0;
}
- public void create(GpuMat depthImage, int mode)
- {
- inputDepthImage = depthImage;
-
- this.mode = mode;
- try
- {
- initialize();
- }
- catch (URISyntaxException e)
- {
- throw new RuntimeException(e);
- }
- reset();
- }
-
public void update(RigidBodyTransform sensorToWorldTransform, RigidBodyTransform sensorToGroundTransform, RigidBodyTransform groundToWorldTransform)
{
int error;
@@ -476,7 +473,7 @@ public void populateParameterArray(HeightMapParameters parameters, CameraIntrins
(float) parameters.getFastSearchSize()};
}
- public static HeightMapData packHeightMapData(RapidHeightMapExtractorCUDA heightMapExtractor, HeightMapData heightMapDataToPack)
+ public static HeightMapData packHeightMapData(RapidHeightMapExtractorInterface heightMapExtractor, HeightMapData heightMapDataToPack)
{
Mat heightMapMat = heightMapExtractor.getTerrainMapData().getHeightMap();
HeightMapData latestHeightMapData = heightMapDataToPack;
diff --git a/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractorInterface.java b/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractorInterface.java
new file mode 100644
index 00000000000..2fd3bb491c6
--- /dev/null
+++ b/ihmc-perception/src/main/java/us/ihmc/perception/gpuHeightMap/RapidHeightMapExtractorInterface.java
@@ -0,0 +1,24 @@
+package us.ihmc.perception.gpuHeightMap;
+
+import us.ihmc.euclid.transform.RigidBodyTransform;
+import us.ihmc.euclid.tuple3D.Point3D;
+import us.ihmc.perception.camera.CameraIntrinsics;
+import us.ihmc.perception.heightMap.TerrainMapData;
+
+public interface RapidHeightMapExtractorInterface
+{
+ void setDepthIntrinsics(CameraIntrinsics cameraIntrinsics);
+
+ Point3D getSensorOrigin();
+
+ int getSequenceNumber();
+
+ TerrainMapData getTerrainMapData();
+
+ void update(RigidBodyTransform sensorToWorldTransform, RigidBodyTransform sensorToGroundTransform, RigidBodyTransform groundToWorldTransform);
+
+ void reset();
+
+ void destroy();
+
+}
From 9be4064c044b8af69ea444cbc1ba9c518c876eeb Mon Sep 17 00:00:00 2001
From: Nick Kitchel <59104880+PotatoPeeler3000@users.noreply.github.com>
Date: Thu, 19 Dec 2024 14:00:33 -0600
Subject: [PATCH 16/16] :arrow_up: ihmc-robot-data-logger 0.30.2 (#552)
---
example-simulations/build.gradle.kts | 2 +-
ihmc-robot-data-visualizer/build.gradle.kts | 2 +-
ihmc-whole-body-controller/build.gradle | 2 +-
robot-environment-awareness/build.gradle.kts | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/example-simulations/build.gradle.kts b/example-simulations/build.gradle.kts
index cc85058e6fd..2acd3c02d07 100644
--- a/example-simulations/build.gradle.kts
+++ b/example-simulations/build.gradle.kts
@@ -11,7 +11,7 @@ ihmc {
}
mainDependencies {
- api("us.ihmc:ihmc-robot-data-logger:0.29.9")
+ api("us.ihmc:ihmc-robot-data-logger:0.30.2")
api("us.ihmc:ihmc-common-walking-control-modules:source")
api("us.ihmc:ihmc-whole-body-controller:source")
diff --git a/ihmc-robot-data-visualizer/build.gradle.kts b/ihmc-robot-data-visualizer/build.gradle.kts
index 87fc9922ca2..0449ff3c2f4 100644
--- a/ihmc-robot-data-visualizer/build.gradle.kts
+++ b/ihmc-robot-data-visualizer/build.gradle.kts
@@ -19,7 +19,7 @@ mainDependencies {
api("us.ihmc:simulation-construction-set:0.25.1")
api("us.ihmc:ihmc-model-file-loader:source")
api("us.ihmc:ihmc-humanoid-robotics:source")
- api("us.ihmc:ihmc-robot-data-logger:0.29.9")
+ api("us.ihmc:ihmc-robot-data-logger:0.30.2")
}
testDependencies {
diff --git a/ihmc-whole-body-controller/build.gradle b/ihmc-whole-body-controller/build.gradle
index 75ca3f2a77a..e918f6ee8ec 100644
--- a/ihmc-whole-body-controller/build.gradle
+++ b/ihmc-whole-body-controller/build.gradle
@@ -16,7 +16,7 @@ ihmc {
mainDependencies {
api("us.ihmc:ihmc-common-walking-control-modules:source")
api("us.ihmc:simulation-construction-set-tools:source")
- api("us.ihmc:ihmc-robot-data-logger:0.29.9")
+ api("us.ihmc:ihmc-robot-data-logger:0.30.2")
}
benchmarksDependencies {
diff --git a/robot-environment-awareness/build.gradle.kts b/robot-environment-awareness/build.gradle.kts
index bf011a3c06b..ddd6fa35dd0 100644
--- a/robot-environment-awareness/build.gradle.kts
+++ b/robot-environment-awareness/build.gradle.kts
@@ -28,7 +28,7 @@ mainDependencies {
api("us.ihmc:joctomap:1.12.5")
api("us.ihmc:ihmc-graphics-javafx:source")
api("us.ihmc:ihmc-messager-javafx:0.2.0")
- api("us.ihmc:ihmc-robot-data-logger:0.29.9")
+ api("us.ihmc:ihmc-robot-data-logger:0.30.2")
api("us.ihmc:ihmc-ros-tools:source")
val openblasVersion = "0.3.23-1.5.9"