xviz数据从rosbag到xvizData源码解读

366 阅读2分钟

1. bagReader读取bag数据(读取工具定义:bag-reader.ts,message结构来源:load-service/cloud.ts

第一步import { open, TimeUtil, Connection } from '@xviz/rosbag'

  • open: 这是一个函数,用于打开和读取 ROS bag 文件。它返回一个 Bag 对象,你可以使用这个对象来访问文件中的数据。

    const bag = await open('path_to_your_bag_file.bag');
    ---
    async open(path: string) {
    await decompress.isLoaded;
    await open(path).then((bag: any) => {
      this.ready = true
      this.bagInstance = bag
      this.currentAt = bag.startTime
    
      let chunkStartTime
      let chunkEndTime
      ...
    
  • TimeUtil: 这是一个包含各种时间操作函数的工具类。用于处理 ROS 中的时间戳和持续时间等时间相关问题。

  • Connection: 它代表与特定主题的连接,可以通过它获取该主题上的消息。当你从 Bag 对象中读取数据时,通常会为每个主题创建一个 Connection

 getConnections() {
    return Object.entries<Connection>(this.bagInstance.connections).map(([_, v]) => {
      return { callerid: v.callerid, topic: v.topic, data_type: v.type, md5sum: v.md5sum, message_define: v.messageDefinition }
    })
  }

读取结果

截屏2023-08-17 下午2.54.10.png

第二步:import { bagReaderFactory } from '@mviz/reader'

 await this.reader.readAt(async (topic, message_type, message, read_time, msg_stamp) => {
      if (this.topicList.includes(topic)) {
        if (this.topicMsgTypeMap[topic] && this.topicMsgTypeMap[topic] !== message_type) console.warn(`[load-service] topic ${topic} expected type ${this.topicMsgTypeMap[topic]} but get ${message_type}`)
        if (!messageQueue[topic]) messageQueue[topic] = []
        messageQueue[topic].push({ topic, message_type, message, timestamp: msg_stamp })
      }
    }, this.currentTimestamp)

获取当前帧topic和message数据:

截屏2023-08-17 下午3.07.11.png 整合为loader后接入transformer进行formater,parser,builder以及后续读写操作

2. 在transformer中处理loader,构建rawData(load-service/cloud.ts&&transform-service/plugin.ts)

 同上文:
await this.reader.readAt(async (topic, message_type, message, read_time, msg_stamp) => {
      if (this.topicList.includes(topic)) {
        if (this.topicMsgTypeMap[topic] && this.topicMsgTypeMap[topic] !== message_type) console.warn(`[load-service] topic ${topic} expected type ${this.topicMsgTypeMap[topic]} but get ${message_type}`)
        if (!messageQueue[topic]) messageQueue[topic] = []
        messageQueue[topic].push({ topic, message_type, message, timestamp: msg_stamp })
      }
    }, this.currentTimestamp)
 //处理 `messageQueue` 中的所有消息,对每条消息进行预处理,并根据其主题发射相应的事件
Object.keys(messageQueue).forEach((topic) => {
      messageQueue[topic].forEach((data, idx) => {
        this.count = this.count + this.topicSubscribeCount[topic]
        const topicEventData = preformat(data)
        this.topicEventEmitter.emit(topic, topicEventData)
      })
    })
 //构建formater原始输入
 this.pluginConfig.topicFormatters.forEach(({ topic }) => {
        this.loader.subscribe(topic, (rawData) => {
          this.lastData = rawData
          this.worker.postMessage({
            rawData,
            privateData: this.privateData,
            sharedData: { ...this.sharedData, preprocessedData: this.preprocessedData },
          })
        })
      })

原始数据(rawData)

截屏2023-08-17 上午11.15.11.png

3. formater&&parser消息处理

上文中已经得到formater的原始数据rawData,作为formater的消息输入

第一步:激活所有插件

  plugins.forEach((plugin) => plugin.activate())
const { topic, message } = rawData
//formater的输入规范
this.formatters[topic]({ topic, message, lastLatchedPrivateData })
//formater的输出规范
      const { normalPrivateData, latchedPrivateData, latchedSharedData } = formattedData

formater输出如图:

截屏2023-08-17 上午11.17.57.png

第二步:触发所有插件的parser

await Promise.all(plugins.map((plugin) => plugin.parse()))
await Promise.all(customPlugins?.map((plugin) => plugin.parse()))
//parser的输入规范
this.worker.postMessage({
          rawData: { triggerParse: true },
          privateData: this.privateData,
          sharedData: { ...this.sharedData, preprocessedData: this.preprocessedData },
          cachedIntermediateResult: this.cachedIntermediateResult,
        })
//parser的输出规范需要满足xviz格式(具体类型定义根据实际绘制为准)

如图:

截屏2023-08-17 上午11.21.43.png

4. xviz服务端写出xviz数据

对面parser的结果派发符合xivz数据格式的消息类型

  dispatch: function (builderData, pluginConfig) {
    const { streamName, uiPreference, coordinate, builtin } = pluginConfig
    builderDatas[streamName] = {
      builderData,
      pluginConfig: { streamName, uiPreference, coordinate, builtin },
    }
  }
  
    Object.keys(builderDatas).forEach((streamName, idx) => {
          if (pluginConfigMaps[keys[i]].includes(streamName)) {
            try {
              builderService.onBuildMessage({ buildMessage: builderDatas[streamName].builderData, pluginConfig: builderDatas[streamName].pluginConfig, frameTimestamp, frameBuilder })
            } catch (error) {
              console.error('onBuildMessage error', error)
            }
          }
        })

定义outputpath&&format

 const path = `${outputPath}/${key}`
     ensureDirSync(path)
     const sink = new FileSink(path)
     let writer = new XVIZFormatWriter(sink, {
       format: XVIZ_FORMAT.BINARY_PBE,
     })