All Downloads are FREE. Search and download functionalities are using the official Maven repository.

us.ihmc.scs2.session.mcap.MCAPFrameTransformManager Maven / Gradle / Ivy

The newest version!
package us.ihmc.scs2.session.mcap;

import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.set.hash.TIntHashSet;
import us.ihmc.euclid.referenceFrame.ReferenceFrame;
import us.ihmc.euclid.transform.interfaces.RigidBodyTransformReadOnly;
import us.ihmc.log.LogTools;
import us.ihmc.scs2.definition.visual.ColorDefinitions;
import us.ihmc.scs2.definition.yoGraphic.YoGraphicDefinition;
import us.ihmc.scs2.definition.yoGraphic.YoGraphicDefinitionFactory;
import us.ihmc.scs2.definition.yoGraphic.YoGraphicGroupDefinition;
import us.ihmc.scs2.session.mcap.MCAPBufferedChunk.ChunkBundle;
import us.ihmc.scs2.session.mcap.MCAPSchema.MCAPSchemaField;
import us.ihmc.scs2.session.mcap.encoding.CDRDeserializer;
import us.ihmc.scs2.session.mcap.specs.MCAP;
import us.ihmc.scs2.session.mcap.specs.records.Channel;
import us.ihmc.scs2.session.mcap.specs.records.Message;
import us.ihmc.scs2.session.mcap.specs.records.Opcode;
import us.ihmc.scs2.session.mcap.specs.records.Record;
import us.ihmc.scs2.session.mcap.specs.records.Schema;
import us.ihmc.yoVariables.euclid.YoPoint3D;
import us.ihmc.yoVariables.euclid.YoPose3D;
import us.ihmc.yoVariables.euclid.YoQuaternion;
import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePoint3D;
import us.ihmc.yoVariables.euclid.referenceFrame.YoFramePose3D;
import us.ihmc.yoVariables.euclid.referenceFrame.YoFrameQuaternion;
import us.ihmc.yoVariables.registry.YoRegistry;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public class MCAPFrameTransformManager
{
   private static final String FOXGLOVE_PREFIX = "foxglove::FrameTransform.";
   private static final String WORLD_FRAME_NAME = "world";
   private static final String FRAME_FIELD_TYPE = "string";
   private static final String PARENT_FRAME_FIELD_NAME = "parent_frame_id";
   private static final String CHILD_FRAME_FIELD_NAME = "child_frame_id";
   private static final String ROTATION_FIELD_NAME = "rotation";
   private static final String ROTATION_X_FIELD_NAME = "rotation.x";
   private static final String ROTATION_Y_FIELD_NAME = "rotation.y";
   private static final String ROTATION_Z_FIELD_NAME = "rotation.z";
   private static final String ROTATION_W_FIELD_NAME = "rotation.w";
   private static final String TRANSLATION_FIELD_NAME = "translation";
   private static final String TRANSLATION_X_FIELD_NAME = "translation.x";
   private static final String TRANSLATION_Y_FIELD_NAME = "translation.y";
   private static final String TRANSLATION_Z_FIELD_NAME = "translation.z";

   private final YoRegistry registry = new YoRegistry(getClass().getSimpleName());
   private final ReferenceFrame inertialFrame;
   private MCAPSchema foxgloveFrameTransformSchema;
   private final List transformList = new ArrayList<>();
   private final Map rawNameToTransformMap = new LinkedHashMap<>();
   private final Map sanitizedNameToTransformMap = new LinkedHashMap<>();
   private final TIntHashSet channelIds = new TIntHashSet();
   /**
    * Sometimes, tfs are defined with a parent that doesn't exist, they are not yet attached to world.
    */
   private final Set unattachedRootNames = new LinkedHashSet<>();

   private final YoGraphicGroupDefinition yoGraphicGroupDefinition = new YoGraphicGroupDefinition("FoxgloveFrameTransforms");
   private Schema mcapSchema;

   public MCAPFrameTransformManager(ReferenceFrame inertialFrame)
   {
      this.inertialFrame = inertialFrame;
   }

   public void initialize(MCAP mcap, MCAPBufferedChunk chunkBuffer) throws IOException
   {
      for (Record record : mcap.records())
      {
         if (record.op() != Opcode.SCHEMA)
            continue;

         mcapSchema = (Schema) record.body();
         if (mcapSchema.name().equalsIgnoreCase("foxglove::FrameTransform"))
         {
            if (mcapSchema.encoding().equalsIgnoreCase("ros2msg"))
            {
               foxgloveFrameTransformSchema = ROS2SchemaParser.loadSchema(mcapSchema);
            }
            else if (mcapSchema.encoding().equalsIgnoreCase("omgidl"))
            {
               foxgloveFrameTransformSchema = OMGIDLSchemaParser.loadSchema(mcapSchema);
            }
            else
            {
               throw new UnsupportedOperationException("Unsupported encoding: " + mcapSchema.encoding());
            }
            break;
         }
      }

      if (foxgloveFrameTransformSchema == null)
      {
         LogTools.error("Could not find the schema for foxglove::FrameTransform");
         return;
      }

      // Flatten the schema to make it easier to read.
      foxgloveFrameTransformSchema = foxgloveFrameTransformSchema.flattenSchema();
      for (String fieldName : Arrays.asList(PARENT_FRAME_FIELD_NAME,
                                            CHILD_FRAME_FIELD_NAME,
                                            ROTATION_FIELD_NAME,
                                            ROTATION_X_FIELD_NAME,
                                            ROTATION_Y_FIELD_NAME,
                                            ROTATION_Z_FIELD_NAME,
                                            ROTATION_W_FIELD_NAME,
                                            TRANSLATION_FIELD_NAME,
                                            TRANSLATION_X_FIELD_NAME,
                                            TRANSLATION_Y_FIELD_NAME,
                                            TRANSLATION_Z_FIELD_NAME))
      {
         if (foxgloveFrameTransformSchema.getFields().stream().noneMatch(field -> field.getName().equalsIgnoreCase(fieldName)))
            throw new RuntimeException("Could not find the field " + fieldName + " in the schema for foxglove::FrameTransform");
      }

      TIntObjectHashMap channelIdToTopicMap = new TIntObjectHashMap<>();
      for (Record record : mcap.records())
      {
         if (record.op() == Opcode.CHANNEL)
         {
            Channel channel = (Channel) record.body();
            if (channel.schemaId() == foxgloveFrameTransformSchema.getId())
            {
               channelIdToTopicMap.put(channel.id(), channel.topic());
            }
         }
      }
      channelIds.addAll(channelIdToTopicMap.keys());

      Map allTransforms = new LinkedHashMap<>();

      for (Record record : mcap.records())
      {
         if (record.op() == Opcode.MESSAGE)
            processRecord(record, channelIdToTopicMap, allTransforms);
      }

      int numberOfTransforms = allTransforms.size();
      int numberOfChunkWithoutNewTransforms = 0;

      for (ChunkBundle chunkBundle : chunkBuffer.getChunkBundles())
      {
         chunkBundle.requestLoadChunkBundle(true, false, false);

         for (Record record : chunkBundle.getChunkRecords())
         {
            if (record.op() == Opcode.MESSAGE)
               processRecord(record, channelIdToTopicMap, allTransforms);
         }

         if (allTransforms.size() == numberOfTransforms)
            numberOfChunkWithoutNewTransforms++;
         else
            numberOfChunkWithoutNewTransforms = 0;

         if (numberOfChunkWithoutNewTransforms > 5)
            break;

         numberOfTransforms = allTransforms.size();
      }

      for (BasicTransformInfo transformInfo : allTransforms.values())
      {
         if (!allTransforms.containsKey(transformInfo.parentFrameName()) && !transformInfo.parentFrameName().equals(WORLD_FRAME_NAME))
         {
            unattachedRootNames.add(transformInfo.parentFrameName());
         }
      }

      if (!allTransforms.isEmpty())
      {
         LinkedList ordered = sortTransforms(allTransforms);

         while (!ordered.isEmpty())
         {
            BasicTransformInfo basicTransformInfo = ordered.poll();
            YoFoxGloveFrameTransform transform = new YoFoxGloveFrameTransform(basicTransformInfo,
                                                                              rawNameToTransformMap.get(basicTransformInfo.parentFrameName()),
                                                                              inertialFrame,
                                                                              registry);
            yoGraphicGroupDefinition.addChild(YoGraphicDefinitionFactory.newYoGraphicCoordinateSystem3D(transform.rawName,
                                                                                                        transform.poseToRoot,
                                                                                                        0.2,
                                                                                                        ColorDefinitions.SeaGreen()));
            rawNameToTransformMap.put(basicTransformInfo.childFrameName(), transform);
         }
         transformList.addAll(rawNameToTransformMap.values());
         for (YoFoxGloveFrameTransform transform : transformList)
         {
            sanitizedNameToTransformMap.put(transform.sanitizedName, transform);
         }
         yoGraphicGroupDefinition.setVisible(false);
      }
   }

   private void processRecord(Record record, TIntObjectHashMap channelIdToTopicMap, Map allTransforms)
   {
      Message message = (Message) record.body();
      String topic = channelIdToTopicMap.get(message.channelId());

      if (topic == null)
         return;

      BasicTransformInfo transformInfo = extractFromMessage(foxgloveFrameTransformSchema, topic, message);
      allTransforms.put(transformInfo.childFrameName(), transformInfo);
   }

   private static LinkedList sortTransforms(Map allTransforms)
   {
      LinkedList ordered = new LinkedList<>(allTransforms.values());
      ordered.sort((o1, o2) ->
                   {
                      int distanceToRoot1 = 0;
                      int distanceToRoot2 = 0;
                      while (o1 != null)
                      {
                         distanceToRoot1++;
                         o1 = allTransforms.get(o1.parentFrameName());
                      }
                      while (o2 != null)
                      {
                         distanceToRoot1++;
                         o2 = allTransforms.get(o2.parentFrameName());
                      }
                      return Integer.compare(distanceToRoot1, distanceToRoot2);
                   });
      return ordered;
   }

   public void update()
   {
      if (foxgloveFrameTransformSchema == null)
         return;

      for (YoFoxGloveFrameTransform transform : transformList)
      {
         transform.update();
      }
   }

   private final CDRDeserializer cdr = new CDRDeserializer();

   /**
    * Tries to read the given message as a frame transform message.
    *
    * @param message the message to read.
    * @return {@code true} if the message was successfully read, {@code false} otherwise.
    */
   public boolean readMessage(Message message)
   {
      if (foxgloveFrameTransformSchema == null)
         return false;

      if (!channelIds.contains(message.channelId()))
         return false;

      cdr.initialize(message.messageBuffer(), 0, message.dataLength());

      double rx, ry, rz, rw;
      double tx, ty, tz;
      String parentFrameName;
      String childFrameName;
      try
      {
         List fields = foxgloveFrameTransformSchema.getFields();
         rw = 1.0;
         rz = 0.0;
         ry = 0.0;
         rx = 0.0;
         tz = 0.0;
         ty = 0.0;
         tx = 0.0;
         parentFrameName = null;
         childFrameName = null;

         for (int i = 0; i < fields.size(); i++)
         {
            MCAPSchemaField field = fields.get(i);
            if (field.isComplexType())
            {
               if (field.getName().equalsIgnoreCase(ROTATION_FIELD_NAME))
               {
                  MCAPSchemaField xField = fields.get(i + 1);
                  MCAPSchemaField yField = fields.get(i + 2);
                  MCAPSchemaField zField = fields.get(i + 3);
                  MCAPSchemaField wField = fields.get(i + 4);
                  if (!xField.getName().equalsIgnoreCase(ROTATION_X_FIELD_NAME))
                     throw new RuntimeException("Unexpected field name: " + xField.getName());
                  if (!yField.getName().equalsIgnoreCase(ROTATION_Y_FIELD_NAME))
                     throw new RuntimeException("Unexpected field name: " + yField.getName());
                  if (!zField.getName().equalsIgnoreCase(ROTATION_Z_FIELD_NAME))
                     throw new RuntimeException("Unexpected field name: " + zField.getName());
                  if (!wField.getName().equalsIgnoreCase(ROTATION_W_FIELD_NAME))
                     throw new RuntimeException("Unexpected field name: " + wField.getName());
                  rx = cdr.readTypeAsDouble(CDRDeserializer.Type.parseType(xField.getType()));
                  ry = cdr.readTypeAsDouble(CDRDeserializer.Type.parseType(yField.getType()));
                  rz = cdr.readTypeAsDouble(CDRDeserializer.Type.parseType(zField.getType()));
                  rw = cdr.readTypeAsDouble(CDRDeserializer.Type.parseType(wField.getType()));
                  i += 4;
               }
               else if (field.getName().equalsIgnoreCase(TRANSLATION_FIELD_NAME))
               {
                  MCAPSchemaField xField = fields.get(i + 1);
                  MCAPSchemaField yField = fields.get(i + 2);
                  MCAPSchemaField zField = fields.get(i + 3);
                  if (!xField.getName().equalsIgnoreCase(TRANSLATION_X_FIELD_NAME))
                     throw new RuntimeException("Unexpected field name: " + xField.getName());
                  if (!yField.getName().equalsIgnoreCase(TRANSLATION_Y_FIELD_NAME))
                     throw new RuntimeException("Unexpected field name: " + yField.getName());
                  if (!zField.getName().equalsIgnoreCase(TRANSLATION_Z_FIELD_NAME))
                     throw new RuntimeException("Unexpected field name: " + zField.getName());
                  tx = cdr.readTypeAsDouble(CDRDeserializer.Type.parseType(xField.getType()));
                  ty = cdr.readTypeAsDouble(CDRDeserializer.Type.parseType(yField.getType()));
                  tz = cdr.readTypeAsDouble(CDRDeserializer.Type.parseType(zField.getType()));
                  i += 3;
               }
            }
            else if (field.getType().equalsIgnoreCase(FRAME_FIELD_TYPE))
            {
               if (field.getName().equalsIgnoreCase(PARENT_FRAME_FIELD_NAME))
               {
                  parentFrameName = cdr.read_string();
               }
               else if (field.getName().equalsIgnoreCase(CHILD_FRAME_FIELD_NAME))
               {
                  childFrameName = cdr.read_string();
               }
            }
            else
            {
               cdr.skipNext(CDRDeserializer.Type.parseType(field.getType()));
            }
         }
      }
      finally
      {
         cdr.finalize(true);
      }

      YoFoxGloveFrameTransform transform = rawNameToTransformMap.get(childFrameName);
      if (transform != null)
      {
         if (!Objects.equals(parentFrameName, transform.parentFrameName))
            LogTools.error(
                  "Unexpected parent frame name: " + parentFrameName + " for child frame: " + childFrameName + " expected: " + transform.parentFrameName);

         transform.poseToParent.getOrientation().set(rx, ry, rz, rw);
         transform.poseToParent.getPosition().set(tx, ty, tz);
         transform.markPoseToRootAsDirty();
      }
      else
      {
         LogTools.error("Could not find transform for child frame: " + childFrameName);
      }
      return true;
   }

   public YoGraphicDefinition getYoGraphic()
   {
      return yoGraphicGroupDefinition;
   }

   public YoRegistry getRegistry()
   {
      return registry;
   }

   public boolean hasMCAPFrameTransforms()
   {
      return foxgloveFrameTransformSchema != null;
   }

   public Schema getMCAPSchema()
   {
      return mcapSchema;
   }

   public MCAPSchema getFrameTransformSchema()
   {
      return foxgloveFrameTransformSchema;
   }

   public YoFoxGloveFrameTransform getTransformFromSanitizedName(String name)
   {
      return sanitizedNameToTransformMap.get(name);
   }

   private static BasicTransformInfo extractFromMessage(MCAPSchema flatSchema, String topic, Message message)
   {
      if (!flatSchema.isSchemaFlat())
         throw new IllegalArgumentException("The schema is not flat.");

      CDRDeserializer cdr = new CDRDeserializer();
      cdr.initialize(message.messageBuffer(), 0, message.dataLength());

      String parentFrameName = null;
      String childFrameName = null;

      for (MCAPSchemaField field : flatSchema.getFields())
      {
         if (field.isComplexType())
            continue;

         if (field.getType().equalsIgnoreCase(FRAME_FIELD_TYPE))
         {
            if (field.getName().equalsIgnoreCase(PARENT_FRAME_FIELD_NAME))
            {
               parentFrameName = cdr.read_string();
            }
            else if (field.getName().equalsIgnoreCase(CHILD_FRAME_FIELD_NAME))
            {
               childFrameName = cdr.read_string();
            }
         }
         else
         {
            cdr.skipNext(CDRDeserializer.Type.parseType(field.getType()));
         }
      }

      cdr.finalize(true);

      if (parentFrameName == null)
         throw new RuntimeException("Could not find the parent frame name for topic: " + topic);
      return new BasicTransformInfo(topic,
                                    Objects.requireNonNull(parentFrameName, "Parent frame name is null for topic: " + topic + " and child: " + childFrameName),
                                    Objects.requireNonNull(childFrameName, "Child frame name is null for topic: " + topic + " and parent: " + parentFrameName));
   }

   private record BasicTransformInfo(String topic, String parentFrameName, String childFrameName)
   {

   }

   public static class YoFoxGloveFrameTransform
   {
      private final String parentFrameName;
      private final String rawName;
      private final String sanitizedName;
      private YoFoxGloveFrameTransform parent;
      private final List children;
      private final YoPose3D poseToParent;
      private final YoFramePose3D poseToRoot;

      private boolean isPoseToRootDirty = true;

      private YoFoxGloveFrameTransform(BasicTransformInfo info, YoFoxGloveFrameTransform parent, ReferenceFrame inertialFrame, YoRegistry registry)
      {
         parentFrameName = info.parentFrameName();
         rawName = info.childFrameName();
         sanitizedName = sanitizeName(rawName);
         children = new ArrayList<>();
         String namePrefix = sanitizedName;
         String worldNamePrefix = sanitizeName(namePrefix + "_world");
         poseToParent = new YoPose3D(namePrefix, registry);
         if (parent == null)
         {
            YoPoint3D yoPosition = poseToParent.getPosition();
            YoQuaternion yoOrientation = poseToParent.getOrientation();
            poseToRoot = new YoFramePose3D(new YoFramePoint3D(yoPosition.getYoX(), yoPosition.getYoY(), yoPosition.getYoZ(), inertialFrame),
                                           new YoFrameQuaternion(yoOrientation.getYoQx(),
                                                                 yoOrientation.getYoQy(),
                                                                 yoOrientation.getYoQz(),
                                                                 yoOrientation.getYoQs(),
                                                                 inertialFrame));
         }
         else
         {
            poseToRoot = new YoFramePose3D(worldNamePrefix, inertialFrame, registry);
         }
         setParent(parent);
      }

      private static String sanitizeName(String name)
      {
         name = name.replace('.', '_').replaceAll("_+", "_");
         return name.startsWith("_") ? name.substring(1) : name;
      }

      public void setParent(YoFoxGloveFrameTransform parent)
      {
         if (this.parent != null)
            throw new IllegalStateException("Parent already set.");
         this.parent = parent;
         if (parent != null)
         {
            if (!parent.rawName.equals(parentFrameName))
               throw new IllegalArgumentException("Unexpected parent frame name: " + parent.rawName + " expected: " + parentFrameName);
            parent.addChild(this);
         }
      }

      public void addChild(YoFoxGloveFrameTransform child)
      {
         children.add(child);
      }

      public void markPoseToRootAsDirty()
      {
         isPoseToRootDirty = true;

         for (YoFoxGloveFrameTransform child : children)
         {
            child.markPoseToRootAsDirty();
         }
      }

      public void update()
      {
         if (parent != null && isPoseToRootDirty)
         {
            if (parent.isPoseToRootDirty)
               parent.update();
            poseToRoot.set(parent.poseToRoot);
            poseToRoot.multiply(poseToParent);
         }
         isPoseToRootDirty = false;
      }

      public String getRawName()
      {
         return rawName;
      }

      public String getSanitizedName()
      {
         return sanitizedName;
      }

      public YoFoxGloveFrameTransform getParent()
      {
         return parent;
      }

      public RigidBodyTransformReadOnly getTransformToParent()
      {
         return poseToParent;
      }

      public RigidBodyTransformReadOnly getTransformToRoot()
      {
         return poseToRoot;
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy