LidarView源码分析(五)lqOpenPcapReaction

简介

lqOpenPcapReaction用于打开pcap点云流文件。

打开pcap文件过程:

第一步:点击打开文件按钮

 第二步:选择pcap文件

 第三步:选择传感器配置文件,可以选择高级配置进行更多参数的配置。

第二步中打开的对话框是lqOpenPcapReaction类中的成员函数onTriggered中触发的。该函数是Qt槽函数,通过 lqOpenPcapReaction对应的QAction类的triggered信号触发。该对话框是pqFileDialog类型。

同时在onTriggered函数中调用了lqOpenPcapReaction的静态函数createSourceFromFile(QString fileName),该函数中打开了第三步中的传感器配置对话框(vvCalibrationDialog),用于加载传感器的配置文件,同时调用了lqOpenPcapReaction的静态函数createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog)。

lqOpenPcapReaction的基类是pqReaction。其绑定了一个QAction实例,当该QAction实例发出triggered信号时,调用onTriggered槽函数,该信号槽的绑定在pqReaction的构造函数中实现。因此lqOpenPcapReaction只需要实现虚函数onTriggered槽函数即可。

头文件

lqOpenPcapReaction头文件如下:

#ifndef LQOPENPCAPREACTION_H
#define LQOPENPCAPREACTION_H

#include "applicationui_export.h"

#include "pqReaction.h"
#include "vvCalibrationDialog.h"

#include <vtkObject.h>
/**
 * @ingroup Reactions
 * Reaction to open a pcap
 */
class APPLICATIONUI_EXPORT lqOpenPcapReaction : public pqReaction
{
  Q_OBJECT
  typedef pqReaction Superclass;

public:
  lqOpenPcapReaction(QAction* action);
  static void createSourceFromFile(QString fileName);
  static void createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog);

protected:
  /// Called when the action is triggered.
  void onTriggered() override;

  static void onProgressEvent(vtkObject* caller, unsigned long, void*);

private:
  class vtkObserver;
  Q_DISABLE_COPY(lqOpenPcapReaction)
};

#endif // LQOPENPCAPREACTION_H

源文件

lqOpenPcapReaction继承自pqReaction,在pqReaction的构造函数中会自动将onTriggered与QAction的triggered信号进行连接。lqOpenPcapReaction的构造函数中调用的其基类(pqReaction)的构造函数。

lqOpenPcapReaction::lqOpenPcapReaction(QAction* action)
  : Superclass(action)
{
}

当lqOpenPcapReaction对应的QAction的triggered信号触发时,调用onTriggered槽函数,该槽函数主要打开了一个选择已存在点云流(pcap)文件的对话框,该对话框类型是pqFileDialog。然后调用了lqOpenPcapReaction中的静态函数createSourceFromFile(fileName)。代码如下:

void lqOpenPcapReaction::onTriggered()
{
  // Get the pcap filename
  // pqSettings是对QSetting的扩展,其支持保存以及恢复窗口以及对话框的几何设置。
  pqSettings* settings = pqApplicationCore::instance()->settings();
  const char* defaultDir_path = "LidarPlugin/OpenData/DefaultDir";
  QString defaultDir = settings->value(defaultDir_path, QDir::homePath()).toString();
  // 打开对话框,进行文件选额,对话框模式为打开已有文件。pqFileDialog也提供打开远程文件功能。
  pqFileDialog dial(pqActiveObjects::instance().activeServer(),
    pqCoreUtilities::mainWidget(),
    tr("Open LiDAR File"),
    defaultDir,
    tr("Wireshark Capture (*.pcap)"));
  dial.setObjectName("LidarFileOpenDialog");
  dial.setFileMode(pqFileDialog::ExistingFile);
  if (dial.exec() == QDialog::Rejected)
  {
    return;
  }
  // 获取选中的文件列表中第一个文件的名称。
  QString fileName = dial.getSelectedFiles().at(0);
  QFileInfo fileInfo(fileName); //QFileInfo用于获取文件信息。
  // 保存当前文件所在的文件夹路径,用于下次打开时的默认路径
  settings->setValue(defaultDir_path, fileInfo.absolutePath());
  
  // 该函数中打开了传感器配置对话框。
  lqOpenPcapReaction::createSourceFromFile(fileName);
}

createSourceFromFile该函数在该类中有2个重载,在本次调用中会再次调用其另一个重载createSourceFromFile(fileName, dialog)。其中dialog的类型是vvCalibrationDialog,用于设置传感器的配置参数,包括解译器的类型,解译器的配置文件,雷达位姿,是否开启GPS等。其界面在简介中的第三步中有展示。

createSourceFromFile(QString)代码如下:

void lqOpenPcapReaction::createSourceFromFile(QString fileName)
{
  // Launch the calibration Dialog before creating the Source to allow to cancel the action
  // (with the "cancel" button in the dialog)
  vvCalibrationDialog dialog(lqLidarViewManager::instance()->getMainWindow(), false);
  // DisplayDialogOnActiveWindow(dialog);
  if (dialog.exec())
  {
    lqOpenPcapReaction::createSourceFromFile(fileName, dialog);
  }
}

在createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog)中,完成了数据读取器以及雷达数据帧的追踪滤波器的创建,并将其加载到界面上(渲染管线浏览器以及属性配置器)。其使用了创建者模式(pqObjectBuilder)进行读取器和滤波器的创建。功能调用使用了代理模式(vtkSMProxy)来完成对应功能的实现。目前读取器(雷达数据解译器)支持HESAI和VELODYNE雷达。

createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog)代码如下:

void lqOpenPcapReaction::createSourceFromFile(QString fileName, const vvCalibrationDialog& dialog)
{
  // 获取当前激活的服务
  pqServer* server = pqActiveObjects::instance().activeServer();
  // pqObjectBuilder是一个不严格的创建者设计模式
  // 它用于创建和销毁复杂对象,如视图,显示器,数据源等。
  // 通过pqApplicationCore单例模式可以获取pqObjectBuilder实例。
  pqObjectBuilder* builder = pqApplicationCore::instance()->getObjectBuilder();
  // 在vtkSMParaViewPipelineController的基础上添加了初始化合适的渲染方式的功能。
  // vtkSMParaViewPipelineController用于控制ParaView的逻辑控制
  vtkNew<vtkSMParaViewPipelineControllerWithRendering> controller;
  // PQ抽象的一般视图模型,用于作为视图的基类。其子类可以指定不同方式的渲染方式,比如三维渲染视图,直方图。
  pqView* view = pqActiveObjects::instance().activeView();

  // Create a progress bar so the user see that LidarView is running
  // 选定pcap文件以及配置文件后的打开文件的进度条
  QProgressDialog progress("Reading pcap", "", 0, 0, lqLidarViewManager::getMainWindow());
  progress.setCancelButton(nullptr);
  progress.setModal(true);
  progress.show();

  // vtkProcessModule是ParaView进程的进程初始化和会话管理核心。
  vtkProcessModule* pm = vtkProcessModule::GetProcessModule();
  // vtkSession的子类,vtkPVSession扩展了vtkSession,用于对ParaView seesion添加API
  // vtkPVSession添加ParaView规范的APIs到vtkSession。即这些APIs用于在数据服务,渲染服务以及客户端之间通信。
  // vtkPVSession是抽象类
  vtkPVSession* session = vtkPVSession::SafeDownCast(pm->GetSession());
  if (!session)
  {
    return;
  }

  // vtkPVProgressHandler处理进度信息。
  vtkSmartPointer<vtkPVProgressHandler> handler = session->GetProgressHandler();
  handler->PrepareProgress();
  double interval = handler->GetProgressInterval();
  handler->SetProgressInterval(0.05);
  vtkNew<vtkObserver> obs;
  unsigned long tag = handler->AddObserver(vtkCommand::ProgressEvent, obs);

  // Remove all Streams (and every filter depending on them) from pipeline Browser
  // Thanks to the lqSensorListWidget,
  // if a LidarStream is delete, it will automatically delete its PositionOrientationStream.
  // So we just have to delete all lidarStream.
  RemoveAllProxyTypeFromPipelineBrowser<vtkLidarStream>();

  if (!dialog.isEnableMultiSensors())
  {
    // We remove all lidarReader (and every filter depending on them) in the pipeline
    // Thanks to the lqSensorListWidget,
    // if a LidarReader is delete, it will automatically delete its PositionOrientationReader.
    // So we just have to delete all lidarReader.
    RemoveAllProxyTypeFromPipelineBrowser<vtkLidarReader>();
  }

  // Create the lidar Reader
  // We have to use pqObjectBuilder::createSource to add the created source to the pipeline
  // The source will be created immediately so the signal "sourceAdded" of the pqServerManagerModel
  // is send during "create source".
  // To get the pqPipelineSource modified with the new property, you have to connect to the signal
  // "dataUpdated" of the pqServerManagerModel
  // 在server上创建名称为"sources"的数据源,该数据源的服务管理群组为"LidarReader"。
  pqPipelineSource* lidarSource = builder->createSource("sources", "LidarReader", server);
  lidarSource->setModifiedState(pqProxy::UNMODIFIED);
  vtkSMPropertyHelper(lidarSource->getProxy(), "FileName").Set(fileName.toStdString().c_str());
  lidarSource->getProxy()->UpdateProperty("FileName"); // 将文件的全路径更新到服务器

  // For Hesai, enable advanced arrays by default
  vtkSMProxy* interpProxy =
    vtkSMPropertyHelper(lidarSource->getProxy(), "PacketInterpreter").GetAsProxy();
  vtkSmartPointer<vtkLidarPacketInterpreter> interp =
    vtkLidarPacketInterpreter::SafeDownCast(interpProxy->GetClientSideObject());
  unsigned int found = interp->GetSensorInformation().find("Hesai");
  if (found != std::string::npos)
  {
    vtkSMPropertyHelper(interpProxy, "EnableAdvancedArrays").Set(1);
    interpProxy->UpdateProperty("PacketInterpreter");
    lidarSource->getProxy()->UpdateProperty("PacketInterpreter");
  }
  QString lidarName = lidarSource->getSMName();

  pqPipelineSource* posOrSource = nullptr;
  QString posOrName = "";

  // Update lidarSource and posOrSource
  // If the GPS interpretation is asked, the posOrsource will be created in the
  // lqUpdateCalibrationReaction because it has to manage it if the user enable interpreting GPS
  // packet after the first instantiation
  lqUpdateCalibrationReaction::UpdateCalibration(lidarSource, posOrSource, dialog);
  // vtkSMProxy* lidarProxy = lidarSource->getProxy();

  if (posOrSource)
  {
    posOrSource->updatePipeline();
    posOrName = posOrSource->getSMName();
    controller->Show(posOrSource->getSourceProxy(), 0, view->getViewProxy());
  }

  // Create the trailing Frame filter on the output of the LidarReader
  QMap<QString, QList<pqOutputPort*>> namedInputs;
  QList<pqOutputPort*> inputs;
  inputs.push_back(lidarSource->getOutputPort(0));
  namedInputs["Input"] = inputs;
  pqPipelineSource* trailingFrameFilter =
    builder->createFilter("filters", "TrailingFrame", namedInputs, server);
  trailingFrameFilter->setModifiedState(pqProxy::UNMODIFIED);

  // 在GUI上的Pipline Browser上显示LidarReader1以及TrailingFrame1
  // Set the trailing frame associated to the sensor Widget
  lqSensorListWidget* listSensor = lqSensorListWidget::instance();
  listSensor->setSourceToDisplayToLidarSourceWidget(lidarSource, trailingFrameFilter);

  // Update applogic to be able to use function only define in applogic.
  lqLidarViewManager::instance()->runPython(
    QString("lv.UpdateApplogicReader('%1', '%2')\n").arg(lidarName, posOrName));

  // Show the trailing frame
  controller->Show(trailingFrameFilter->getSourceProxy(), 0, view->getViewProxy());
  pqActiveObjects::instance().setActiveSource(trailingFrameFilter);
  pqApplicationCore::instance()->render();

  // WIP Workaround Seek to first Frame To prevent displaying half frame from begining
  pqAnimationScene* animScene =
    pqPVApplicationCore::instance()->animationManager()->getActiveScene();
  if (animScene)
  {
    QList<double> timesteps = animScene->getTimeSteps();
    if (!timesteps.empty())
    {
      animScene->setAnimationTime(timesteps.first());
    }
  }

  // Remove the handler so the user can interact with LidarView again (pushing any button)
  handler->RemoveObserver(tag);
  handler->LocalCleanupPendingProgress();
  handler->SetProgressInterval(interval);
  progress.close();
}

当加载雷达数据时,会显示一个进度信息,该进度信息是由vtk的观察者/命令模式进行控制,QProgressDialog进行展示。因此在该源文件的开头定义了基类为vgtCommand的类vtkObserver,代码如下:

class lqOpenPcapReaction::vtkObserver : public vtkCommand
{
public:
  static vtkObserver* New()
  {
    vtkObserver* obs = new vtkObserver();
    return obs;
  }

  void Execute(vtkObject*, unsigned long eventId, void*) override
  {

    if (eventId == vtkCommand::ProgressEvent)
    {
      QApplication::instance()->processEvents();
    }
  }
};

在源代码实现中,大量运用了创建者模式(pqObjectBuilder)和代理模式(vtkSMProxy)。

vtkSMProxy:

服务器上VTK对象的代理。

vtkSMProxy使用代理模式管理在服务器上创建的VTK对象。被管理对象通过属性进行操作。

由vtkSMProxy创建和管理的对象类型由VTKClassName变量决定。通过从代理获取所需的属性、更改其值并使用UpdateVTKObjects()更新服务器来管理该对象。

代理可以是复合的。代理管理器可以添加子代理。这对用户来说是透明的,用户可以看到所有属性,就好像它们属于根代理一样。

一个代理有一个iVar的连接ID。该连接ID表示一个存在该代理的连接。虽然,连接ID能够让一个客户端连接多个服务端。但在ParaView中,因为ParaView只存在一个服务端,因此该ID不重要。连接ID必须在创建代理时马上进行设置,在这之后改变连接ID的行为是危险的。

 * Once a proxy has been defined, it can be listed in another secondary group
 * \code
 * <ProxyGroup name="new_group">
 *  < Proxy group = "group" name ="proxyname" />
 * </ProxyGroup>
 * \endcode
 *
 * When defining a proxy in the XML configuration file,
 * to derive the property interface from another proxy definition,
 * we can use attributes "base_proxygroup" and "base_proxyname" which
 * identify the proxy group and proxy name of another proxy. Base interfaces
 * can be defined recursively, however care must be taken to avoid cycles.
 *
 * There are several special XML features available for subproxies.
 * \li 1) It is possible to share properties among subproxies.
 *    eg.
 *    \code
 *    <Proxy name="Display" class="Alpha">
 *      <SubProxy>
 *        <Proxy name="Mapper" class="vtkPolyDataMapper">
 *          <InputProperty name="Input" ...>
 *            ...
 *          </InputProperty>
 *          <IntVectorProperty name="ScalarVisibility" ...>
 *            ...
 *          </IntVectorProperty>
 *            ...
 *        </Proxy>
 *      </SubProxy>
 *      <SubProxy>
 *        <Proxy name="Mapper2" class="vtkPolyDataMapper">
 *          <InputProperty name="Input" ...>
 *            ...
 *          </InputProperty>
 *          <IntVectorProperty name="ScalarVisibility" ...>
 *            ...
 *          </IntVectorProperty>
 *            ...
 *        </Proxy>
 *        <ShareProperties subproxy="Mapper">
 *          <Exception name="Input" />
 *        </ShareProperties>
 *      </SubProxy>
 *    </Proxy>
 *    \endcode
 *    Thus, subproxies Mapper and Mapper2 share the properties that are
 *    common to both; except those listed as exceptions using the "Exception"
 *    tag.
 *
 * \li 2) It is possible for a subproxy to use proxy definition defined elsewhere
 *     by identifying the interface with attributes "proxygroup" and "proxyname".
 *     eg.
 *     \code
 *     <SubProxy>
 *       <Proxy name="Mapper" proxygroup="mappers" proxyname="PolyDataMapper" />
 *     </SubProxy>
 *     \endcode
 *
 * \li 3) It is possible to scope the properties exposed by a subproxy and expose
 *     only a fixed set of properties to be accessible from outside. Also,
 *     while exposing the property, it can be exposed with a different name.
 *     eg.
 *     \code
 *     <Proxy name="Alpha" ....>
 *       ....
 *       <SubProxy>
 *         <Proxy name="Mapper" proxygroup="mappers" proxyname="PolyDataMapper" />
 *         <ExposedProperties>
 *           <Property name="LookupTable" exposed_name="MapperLookupTable" />
 *         </ExposedProperties>
 *       </SubProxy>
 *     </Proxy>
 *     \endcode
 *     Here, for the proxy Alpha, the property with the name LookupTable from its
 *     subproxy "Mapper" can be obtained by calling GetProperty("MapperLookupTable")
 *     on an instance of the proxy Alpha. "exposed_name" attribute is optional, if
 *     not specified, then the "name" is used as the exposed property name.
 *     Properties that are not exposed are treated as
 *     non-saveable and non-animateable (see vtkSMProperty for details).
 *     Exposed property restrictions only work when
 *     using the GetProperty on the container proxy (in this case Alpha) or
 *     using the PropertyIterator obtained from the container proxy. If one
 *     is to some how obtain a pointer to the subproxy and call GetProperty on
 *     it (or get a PropertyIterator for the subproxy), the properties exposed
 *     by the container class are no longer applicable.
 *     If two exposed properties are exposed with the same name, then a Warning is
 *     flagged -- only one of the two exposed properties will get exposed.

updateCalibration:读取配置信息。其中调用了lqUpdateCalibrationReaction::setReaderCalibration中选择了雷达数据解译器的具体实现。其代码如下:

// Applicatoin/Ui/lqUpdateCalibrationReaction.cxx > setReaderCalibration  
    if (interpreter == vvCalibration::Plugin::HESAI)
    {
      // Set General Packet Interpreter based on SDK as default
      defaultProxy =
        proxyListDomain->FindProxy("LidarPacketInterpreter", "HesaiGeneralPacketInterpreter");
      // Set Custom Interpreter for Pandar128 if name implies it
      if ((calibrationFile.contains("Pandar128", Qt::CaseInsensitive)))
      {
        defaultProxy =
          proxyListDomain->FindProxy("LidarPacketInterpreter", "HesaiPacketInterpreter");
      }
    }
    else if (interpreter == vvCalibration::Plugin::VELODYNE)
    {
      // Set Meta Interpreter as Default
      defaultProxy =
        proxyListDomain->FindProxy("LidarPacketInterpreter", "VelodyneMetaPacketInterpreter");
    }
    else
    {
      qCritical() << "Unknown Interpreter Type";
      return;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值